Click here to Skip to main content
Click here to Skip to main content
Go to top

Improving Code Auto Completion in C#

, 16 Sep 2009
Rate this:
Please Sign up or sign in to vote.
An article on improving code completion in C#.

Sample Image

Introduction

Code snippets are an excellent feature of Visual Studio, that can greatly improve productivity. However, I feel that in C#, they are slightly lacking in two areas:

  • They do not auto complete open brackets with close brackets.
  • For the most generally used snippets, the typing required to get the statement to appear is unnatural (having to press Tab).

For example, for an if statement's code snippet to fire, you have to type "i", then "f", then "tab", then "tab". It is surely better to type "i", then "f", then "space", and have the code snippet appear. It is more natural and has one less key press. I wonder at the percentage of C# coders who do not even know code snippets exist because of this!

Fortunately, Microsoft has made it easy to override this behaviour by using extensibility to check what text the user has just entered, and then manipulating it.

The add-in does three things:

  1. Check the entered text for code statements such as if, switch, for, foreach, and run the corresponding code snippet after a space or newline is entered.
  2. Check for an open bracket such as (, [, and then automatically add the corresponding close bracket.
  3. After an opening brace {, a corresponding closing brace } will be added.

These options can be configured by the user.

Background

A great background to Visual Studio Extensibility is the excellent LineCounter project on this site.

Installation

Unzip the demo files to a folder somewhere. Move the "Jonno C# AutoComplete.AddIn" file to your "your path\My Documents\Visual Studio 2008\Addins" folder. Open the file up and change the following line to point to the location of the files:

<Assembly>C:\Jonno\Jonno.AddIns.CSharp.AutoComplete\bin\
          Jonno.AddIns.CSharp.AutoComplete.dll</Assembly>

If you are running the source, then the "Jonno C# AutoComplete.AddIn" file will probably be missing. Add it to the add-in project, making sure you link it to the version in your add-ins folder, rather than adding it.

If you wish to run the Unit Tests, you will need NUnit 2.5 and Rhino Mocks 3.6.

I have only tested this on Visual Studio 2008.

Options

The options form can be loaded from the menu entry under Tools->Jonno C# Auto Complete Options.

The options are saved as XML files in the same folder as the add-in assembly.

There are three tabs on the form: Snippets, Brackets, and Braces. Let's examine each one in turn.

Snippets

Sample Image

This is simply a grid view of the snippets that will be checked. If you don't like an entry, then simply delete it. If you feel something is missing, then add it, or edit an existing entry.

The text in the grid view is the text that will be checked, the last character of the string is the key press that will fire the code snippet.

For example, the first entry " if " means that when the user presses space (the last character), it will check to see if the previous text was " if". If it was, then the keypress will be cancelled and tab sent to the window to fire the code snippet.

The entry " if/r" will do the same thing except that it fires the code snippet on a newline rather than a space.

So, if you want the code snippet for a do statement to fire after typing "do ", add the string " do " to the list. If you also want it fired on a newline, add the string " do\r", or if you don't want it to fire, delete it from the list.

Obviously, for this to work, your code snippet must have a shortcut that matches the text in the list; i.e., adding " xxx " to the list will not do much if you don't have a code snippet with a shortcut of xxx!

Brackets

Sample Image

Again, this is a grid view. The first column is the opening bracket to search for, the second column is the closing bracket that will be automatically added.

So, putting ( as the first character and ) as the second means that a ) will automatically be added when the user presses (.

The first and second characters can be the same for quotes, e.g., ' and ', or " and ".

If the characters are different, then a closing bracket will only be added if there are less closing brackets than opening brackets on that line. If the characters are the same, then the closing quote will only be added if the number of quotes is odd on that line.

Once again, you can add, edit, or delete to suit your tastes.

Braces

Sample Image

The first option Matching works as follows (where pipe | represents the cursor):

public string Myproperty |

pressing { will return:

public string Myproperty { | }

Whereas given:

public void myMethod()
    |

pressing { will return:

public void myMethod()
{
    |
}

The One True Brace differs from this in that opening braces where there is already text on the line produces the following:

public void myMethod() |

Pressing { will return:

public void myMethod() {
    |
}

The One True Brace Newline option works as the previous option, with the difference that the closing brace is only added after the user presses Enter after the original brace.

None turns off the option.

Using the Code

Most of the logic is handled in the VSKeyPressHelper class, so we need a property for this:

private VSKeyPressHelper KeyPressHelper { get; set; }

This is initialised with the application object in the OnConnection method in the Connect class, as so:

this.ApplicationObject = (DTE2)application;
this.AddInInstance = (AddIn)addInInst;

this.KeyPressHelper = new VSKeyPressHelper(this.ApplicationObject);

To hook into the key press events, we need a property of type TextDocumentKeyPressEvents. We also need to hook into the window events with a property of WindowEvents, like so:

private TextDocumentKeyPressEvents TextDocKeyEvents { get; set; }

private WindowEvents WindowEvents { get; set; }

Then, in the OnConnection method in the Connect class, we hook up the Windows events we need, which are the WindowActivated and WindowCreated events.

Events2 events = (Events2)this.ApplicationObject.Events;

this.WindowEvents = (WindowEvents)events.get_WindowEvents(null);
this.WindowEvents.WindowActivated += 
   new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
this.WindowEvents.WindowCreated += 
   new _dispWindowEvents_WindowCreatedEventHandler(this.WindowCreated);

Of course, we need to unhook this event in the OnDisconnection method of the Connect class:

if (this.WindowEvents != null)
{
    this.WindowEvents.WindowActivated -= 
      new _dispWindowEvents_WindowActivatedEventHandler(this.WindowActivated);
    this.WindowEvents.WindowCreated -= 
      new _dispWindowEvents_WindowCreatedEventHandler(this.WindowCreated);
}

In the handler for the WindowActivated and WindowCreated events, we then check whether we are in a C# file or not by checking the last three characters of the window that has been activated:

private void WindowCreated(Window created)
{
    this.AddKeyboardEventsIfFileIsaCSharpFile(created.Caption);            
}

private void WindowActivated(Window gotFocus, Window lostFocus)
{
    this.AddKeyboardEventsIfFileIsaCSharpFile(gotFocus.Caption);
}

private void AddKeyboardEventsIfFileIsaCSharpFile(string fileName)
{
    this.RemoveKeyboardEvents();

    if (fileName.EndsWith(".cs") || fileName.Contains(".cs "))
    {
        this.SetUpKeyboardEventsHandler();
    }
}

If it is a C# file, we then set up the handling of the keyboard events. This means that the add-in only works with C# files and not VB.NET where it is unwanted. We are interested in the BeforeKeyPress and AfterKeypress events. They first get unhooked in the RemoveKeyboardEvents method, and then added in the SetUpKeyboardEventsHandler method.

Events2 events = (Events2)this.ApplicationObject.Events;
this.TextDocKeyEvents = (TextDocumentKeyPressEvents)
                           events.get_TextDocumentKeyPressEvents(null);

this.TextDocKeyEvents.BeforeKeyPress += 
  new _dispTextDocumentKeyPressEvents_BeforeKeyPressEventHandler(this.BeforeKeyPress);
this.TextDocKeyEvents.AfterKeyPress += 
  new _dispTextDocumentKeyPressEvents_AfterKeyPressEventHandler(this.AfterKeyPress);

Let's delve into the BeforeKeyPress method:

// This handles the code snippets checking
if (this.KeyPressHelper.CheckForCodeSnippet(selection, keypress))
{
    cancelKeypress = true;
    
    // sends escape first to exit out of intellisense if it is open
    // if it is not open it does not matter.
    SendKeys.Send("{esc}");
    SendKeys.Send("{tab}");
}

Using the keypress and current selection, the CheckForCodeSnippet determines if a phrase we are interested in has just been entered. If it is, then we send Escape to the active window. This cancels the intellisense if it is open. We then send Tab to the active window, which fires the code snippet (if it exists).

The CheckForCodeSnippetStatement method works by moving backwards from the selection point to get what was entered, then moving the selection point back to where it was, as so:

private string GetPreviousTextFromSelectionPoint(EditPoint ep, EditPoint sp, int length)
{
    sp.CharLeft(length);
    var text = sp.GetText(ep);
    sp.CharRight(length);
    return text;
}

It then compares the text with " if" to see if an "if" statement was entered.

The AfterKeyPress method works in a similar way of manipulating the text around the selection.

switch (keypress)
{
    case "{":   
            // handles normal terse brackets
            this.KeyPressHelper.AddEndBraceAfterOpenBrace(selection);
            break;

    default:
            // handles all other brackets
            this.KeyPressHelper.AddEndBracketAfterOpenBracket(selection, keypress);
            break;
}

The main difference is how the text is manipulated. Instead of sending keys to the active window, we can directly insert text, using the selection and edit points, such as the code that inserts an end bracket:

sp.Insert(reverse);
selection.CharLeft(false, 1);

The first line inserts the bracket, then the selection is moved to the left. Most of the rest of the code in the VSKeyPressHelper class works on similar principles.

A lot of the code in the Connect class deals with creation of tool windows and menus, which I will not detail. Other classes of note are the XMLHelper class which saves and loads the settings to and from XML files, and the OptionsView which is the tool window used to set the options.

Points of Interest

The first annoyance of this type of project is that automated unit testing is extremely hard! Moving the selection point around and inserting text thus involves a bit of trial and error before you get it right. I have added Unit Tests where possible, I feel it was not worth the effort to test the Connect class or the VSKeyPressHelper class.

The most bone headed thing I did was not account for commenting! I added the LineContainsCSharpComments method after using this for a while, which checks for a "//" before the entered text, and turns off the behaviour if it is there.

History

  • 7th September, 2009: Initial version.
  • 14th September, 2009: Added configuration of snippets, brackets, and brace style. Units Tests added to source where practical. Refactored the code base.
  • 15th September, 2009: Added handling of the WindowCreated event.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Paul "Jonno" Johnson
Software Developer (Senior)
United Kingdom United Kingdom
I have over 15 years of development experience, in many different languages, programming styles and platforms. Currently working as a C# coder, and residing in north Herts in the UK. I love lean software development and anything that reduces a grind to leave more time for useful coding!

Comments and Discussions

 
GeneralMy vote of 4 Pinmemberitsho18-Jan-11 22:25 
GeneralRe: My vote of 4 Pinmembermanu01829-Jul-11 2:34 
QuestionVS 2005 and VB.Net support??? Pinmemberkorcutt3-May-10 5:24 
Hello All,
I was curious if something like this has been put together for Visual Studio 2005 and for VB.Net??? Or has anyone looked at porting this over/down to the earlier version?...
 
Thanks in advance,
 
Kevin Orcutt
GeneralAuto Code Snippets in Visual Studio 2008 Pingroupelizas3-Feb-10 3:20 
GeneralTime saving One Pinmemberthatraja15-Jan-10 3:21 
GeneralThank You!! PinmemberRajesh Pillai22-Sep-09 23:13 
GeneralGood addon, but i excpect something more PinmemberNightElfik9-Sep-09 12:35 
GeneralRe: Good addon, but i excpect something more PinmemberPaul "Jonno" Johnson9-Sep-09 23:51 
GeneralAt Last! PinmemberFatGeek9-Sep-09 7:06 
GeneralRe: At Last! Pinmemberpmj29-Sep-09 11:20 
GeneralRe: At Last! PinmemberFatGeek12-Sep-09 0:03 
GeneralGreat, thank you! PinmemberAndromeda Shun8-Sep-09 21:42 
GeneralRe: Great, thank you! Pinmemberpmj28-Sep-09 22:13 
GeneralAwesome! I added closing single and double quotes, too. PinmemberOak Chantosa8-Sep-09 15:41 
GeneralRe: Awesome! I added closing single and double quotes, too. Pinmemberpmj28-Sep-09 22:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140922.1 | Last Updated 16 Sep 2009
Article Copyright 2009 by Paul "Jonno" Johnson
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid