Introduction

The bookmarks feature is something that I never liked much in Visual Studio. I never wanted 100’s of bookmarks. I just wanted some shortcuts for the most frequently used lines/sections.
I didn’t find anything similar and even Microsoft didn’t want to add this feature to Visual Studio, so I went ahead and created this Visual Studio extension.

Numbered Bookmarks is a Visual Studio 2010 extension to create and recall bookmarks by using numbers. It allows the user to create 10 numbered bookmarks (starting from 0 to 9).
The user can add or navigate to the particular bookmark by using the same shortcut key. The tool adds a bookmark margin to the Visual Studio editor, next to the scrollbar. Whenever a bookmark
is created, a visual glyph is placed on the bookmark margin. The user can also create/navigate/clear bookmarks from the Numbered Bookmarks menu under the Tools menu.

Using Numbered Bookmarks is relatively easy:

Create bookmark: Press key combination Ctrl+Alt+Number to create a bookmark, where Number can be any number from 0 to 9.

Navigate to bookmark: Press key combination Ctrl+Alt+Number to move to the bookmark location.

Delete a bookmark: Right click on the bookmark, in the bookmark margin.

Delete all bookmarks: Press key combination Ctrl+Alt+Backspace.

Information about a bookmark: Mouse hover over the bookmark shows basic information about it, which includes the bookmark number,
file name, line number, and column number.

How to use information: Mouse hover over the Green colored bookmark in the bookmark margin shows the basic how to.

Prerequisites

For developing Numbered Bookmarks, we need the following applications pre-installed:

Divide and conquer

The task of explaining a full-flexed Visual Studio extension can be very overwhelming depending on the complexity of the extension. Numbered Bookmarks
is though a simple extension but yes it is a little complicated to explain (more so for a beginner). Let us try to break it into smaller tasks and complete them
one by one, resulting in finding our goal. So, what are we waiting for? Let's get started.

Create the basic infrastructure

We can use the Visual Studio Package Wizard to generate the basic infrastructure. So let's go ahead and create the basics; the steps that you need to follow are:

Go to File->New and in the New Project dialog, select Other Project Types->Extensibility
and select Visual Studio Package in the right pane. Name the package NumberedBookmarks and click OK.

Click Next on the Welcome page of the Visual Studio Package Wizard. Select Visual C# as your programming language and select
Generate a new key to sign the assembly option and click Next.

In the next step, provide information about the package. Modify the icon, company name, package name, package version, and package information, and click Next.

Select the Menu Command option in the next step. It creates a menu entry in the Tools menu with the caption Numbered Bookmarks. Click Next.

In the next step, change the Command Name to Numbered Bookmarks and Command ID to cmdIDNumberedBookmarks and click Next.

Add menus and submenus

Apart from keyboard shortcuts, the user can also use menu options to create, move to, or remove all bookmarks. Let's add the submenus to the Numbered Bookmarks menu created
in the earlier step. Finally our menus should look like this:

First of all, we will add command IDs to PkgCmdIDList.cs, these IDs are used to hook event handlers to the menu entries.
There is already one entry in this class for the Numbered Bookmarks menu. We are not going to use this menu, rather we will add a submenu to the Tools menu, so delete
the entries and create the command IDs for all menu entries. Finally, it looks like:

Now, before going further to make changes to the VSCT (Visual Studio Command Table) file for more menu options, let's create a bitmap menu strip for the menu icons (each icon of 16x16).
Yes, we have to create a bitmap strip. For creating this bitmap strip, place all your images side by side in a new image and save the final image as a bitmap. Below is a sample
of the bitmap strip which is used for Numbered Bookmarks. Don't forget to add it to Resources.

It's time to get our hands dirty. Open the NumberedBookmarks.vsct file. Scroll to the Bitmaps section and modify the section as:

Notice that the images are numbered from 1 and not from 0, the value portion contains the index of the image in the bitmap strip from the left.

Each menu is contained inside a menu group. This way we have two menu groups (shocked?). Let me try to explain. We are adding a menu Numbered Bookmarks under
the Tools menu and another sub menu beneath it. So we have two menus, Numbered Bookmarks menu and submenu. Both of these should have corresponding menu groups too. Let's name
them MyMenuGroup and SubMenuGroup, respectively. All menu options are added to SubMenuGroup finally.

We will divide this step in five parts:

First of all, let's add GUID symbols for all our commands (created earlier) and menus and menu groups. Make sure that all the values match
the command IDs created earlier, otherwise event handlers will not be fired.

Thirdly, add a Menus section and add a menu entry for Numbered Bookmarks. While adding a menu, you can provide a command name and caption to display.
Yes, you cannot provide an icon for a submenu. Sigh!

Fourthly, let's add our sub menu entries to the file now. In order to add a menu, we use a Button tag inside a Buttons tag. We can provide the command name, caption,
and icon for each of the menus. Other entries can also be added similar to the sample below:

Last, let's create keyboard shortcuts for our menus. This is known as key-binding. While adding a key binding, you can provide two modifiers and two keys.
Strange again. Let's try to recall the key combination for Solution Explorer is (Ctrl+W, S), which can be divided in two parts. First being Ctrl+W and second being S. Ctrl is modifier1
and W is key1, similarly S is key2. The modifier can be a combination of Ctrl, Alt, or Shift separated by a space.

Finally, we reach the place where we can bind our commands to the event handlers. In the Initialize function of the package class (in this case,
NumberedBookmarksPackage), we can bind the command with an event handler and in the same class, we can create an event handler for the same.

In the case of Numbered Bookmarks, the functionality of adding bookmarks is the same for all 10 entries (numbers 0 to 9) and in the event handler, we call a function
AddBookmark with an int parameter to specify which bookmark to add. So, I opted for anonymous methods in place of event handlers.

Create bookmark margin

Creating a margin or bookmark margin is a twofold process. We need to create two classes, the first one inherited from IWpfTextViewMarginProvider, and the second
one inherited from IWpfTextViewMargin. We need to export our factory (provider) class to MEF. As per the MEF philosophy 'You export, we import. We export, you import.'
Here we export our provider class and MEF imports it, and this class tells MEF how to create a margin. The second class actually creates the margin.
We have inherited it from Border to provide some basic WPF functionality like background color, width, height, etc.
We could have opted for some other control or a custom control as well.

Let's try to understand all the attributes applied to the MarginFactory class:

Export: Tells what type of export this class provides, which is IWpfTextViewMarginProvider in our case.

Name: Tells the name of the export provided, which is BookmarkMargin.MarginName (a constant) in our case.

Order: Tells MEF to order/arrange multiple instances of the extension.

MarginContainer: This attribute tells the name of the container (pre-defined constant), in our case it is PredefinedMarginNames.Right.
Other options can be Left, Right, Top, Bottom, ScrollBar, ZoomControl, LineNumber,
Spacer, Selection, Glyph, etc.

ContentType: Declares an association of the extension with a particular type of content, which is code in our case.

TextViewRole: Specifies what type of view the extension should be associated with, in our case it is Document. Other options can be Editable,
Debuggable, Zoomable, etc.

class BookmarkMargin : Border, IWpfTextViewMargin
{
...
}

IWpfTextViewMargin represents a margin which is attached to an edge of the Visual Studio Editor (IWPFTextView).

Create bookmark

The bookmark is a very simple WPF custom control with an ellipse added to it. To improve the overall look and feel of the bookmark (called glyph), we polished
it by adding a few resources. We also created a style for the tooltip, which is shown when the mouse moves over the bookmark.

Collect file, line, and column information

In order to get the currently opened document, line number, and column number, we will use the DTE2 object. The DTE2 object represents the Visual Studio .NET IDE
and is the top-most object in the automation model hierarchy. Let's get an instance of DTE2:

We can get an instance of the Visual Studio automation model (DTE) using the GetService function (remember, it is part of the Package class and our
package is inherited from Package). We need to type cast the DTE object to a DTE2 object. Due to some historical reasons (this is how MSDN explains it),
we have to perform these two operations, get DTE object first and then convert it to DTE2. We can get the name of the opened file using
the ActiveDocument property of the DTE2 object.

For line and column number, we need to extract the VirtualPoint object (current cursor location) from the ActiveDocument.Selection.ActivePoint property
of the DTE2 object. Both line and column number starts from 1 (again historical reasons, because it came from the VB scripting world).

Calculate bookmark positions

Calculating bookmark positions is fairly simple but what do we exactly mean with bookmark position? Actually, when the user opts for creating a bookmark at a particular
position, we want to create a bookmark in the margin in a relative place. Let us assume that the line number is 5 and the total number of lines in the document is 20, then
we want our bookmark to be placed somewhere around one-fourth (1/4th) the height of the margin. For calculating the position, we need the total number of lines in the document and
the height of the window (which is the ViewPort). We can get both these things from IWpfTextView. Now the question comes, how to get the IWpfTextView instance.
Simple, it can be retrieved from the TextView property of IWpfTextViewHost. Now the question is how to get
IWpfTextViewHost. Get the instance of SVsTextManager using the GetService function; extract its ActiveView as IVsTextView.
Cast IVsTextView as IVsUserData and get an IWpfTextViewHost instance by calling the GetData function of IVsUserData with
the DefGuidList.guidIWpfTextViewHost pre-defined constant. Isn't it simple?

We already have another bookmark at the same position: In this case, try to find the next available position for the bookmark. Again, if we have
another bookmark at that position, then? Then find the next available position. Overall, what Numbered Bookmarks does in this case is, it keeps on searching for the next available
location towards the bottom and if it is still not able to find any available location, then it starts finding a position towards the top of the window and this way, it makes calculations
for the bookmark position. Another question is how to check if a bookmark is already there at that position? We iterate through all the elements in the bookmark margin
and compare the y-coordinate with the y-position.

Manage all bookmarks

Before coming to all bookmarks, let's talk a little bit about the bookmark. We created a Bookmark class that represents a bookmark. It is a very simple class with public
properties for Number (bookmark number), LineNumber, ColumnNumber, and FileName, and two overloaded constructors to initialize the variables.

For managing all the bookmarks, we created a BookmarkManager class. This class keeps a dictionary (bookmark number is the key and
the object of the Bookmark class is the value) of all bookmarks which are accessible through the public property Bookmarks. It also declares an event
BookmarksUpdated, which gets fired whenever any changes to the dictionary are made. Why this? Simply to update the margin whenever another bookmark is added or removed.
It also provides the functionality to go to a particular bookmark number (location stored with it). We simply find the ProjectItem using
DTE2 and the FileName stored in the bookmark. Open the document and activate it and then move, move cursor to the start of the document,
and then move to the offset provided by LineNumber and ColumnNumber.

The AddBookmark and RemoveBookmark functions simply add an entry to the Bookmarks dictionary and fire the event.

BookmarkGlyph (our WPF custom control) subscribes to the MouseLeftButtonDown and MouseRightButtonDown events and calls GoToBookmark and
RemoveBookmark of the bookmark manager to provide the functionality.

Add bookmarks and handle events

The BookmarkMargin class handles updating the margin by adding or removing bookmarks to it. Basically, this class inherits from
IWpfTextViewMargin and the Border class. We add a Canvas to its children. While adding bookmarks, we add them
to the Canvas object. The BookmarkMargin class holds an instance of BookmarkManager. In the constructor
of the margin, we call the UpdateBookmarks function which in turn calls UpdateBookmark for each bookmark. We remove all the children from
the Canvas and then create all the bookmarks and add them to the children.

CreateBookmarkGlyph and CreateHelpGlyph create an instance of the BookmarkGlyph class with specific properties and return it.

Add help bookmark

Help bookmark is just like any other bookmark with some default/fixed values associated with it. Its bookmark number is 99 (why? because I love it),
and it is always placed in the middle of the bookmark margin. While creating a BookmarkGlyph object, we identify the bookmark and change its properties like
we change the text to question mark, its fill color to a green colored gradient, and provide a different tooltip than a regular bookmark (not providing the code as it is very straightforward).

Export package and bookmark margin

This is one of the most important steps of creating a package. If we fail to specify the content of the package, then it won't work as specified (bookmark margin
will not be created). A most important point to note here is the Description field. If this field is more than 280 characters, then you can't upload it to
the Visual Studio Gallery. You can also provide two images in the manifest, which will be used by the Visual Studio Gallery to show with it.

Let's come back to the content part. Click on the Add Content button and select MEF Component as the content type
and choose Project as a source and select NumberedBookmarks from the dropdown.

Make sure that both VS Package and MEF Component are added to the contents list.

Bind it up

Let's try to understand the system as a whole. MarginFactory creates a BookmarkMargin and also associates a BookmarkManager to this particular instance
of BookmarkMargin. BookmarkMargin creates BookmarkGlyph and adds bookmark glyphs to the margin instance.
BookmarkManager in place keeps a list of all the bookmarks and fires the BookmarksUpdated event when a bookmark is added or removed.
BookmarkGlyph in place keeps a track of the associated BookmarkManager and calls its functions for handling mouse down events. Simple!

Give it a dry run

It's time to give it a try. Build and start the solution (with or without debugging), this starts an instance of Visual Studio which is specially meant for testing
extensions, known as Experimental Instance. Extensions can be debugged in this instance.

Download and installation

The extension can be installed in the following ways:

Build and install: Build the attached solution and double click NumberedBookmarks.vsix in the Release/Debug folder. This will start the installation
of the extension. This is usually a single click installation.

Use Extension Manager: Go to Tools->Extension Manager. Click Online Gallery in the left navigation panel and type Numbered Bookmarks
in the Search Online Gallery text box and click Enter. It will show the extension with a Download button next to it. Click Download and follow the steps
to install the extension.

Download from VS Gallery: Download the extension from this
URL and double click the vsix file downloaded (alternatively, you can choose to run the application).

You can uninstall the extension from Extension Manager, by clicking the Uninstall button next to the extension. Restart Visual Studio for the changes to take effect.

Summary

Creating extensions for Visual Studio is pretty simple and straightforward (not always) but this can be tricky sometimes. Numbered Bookmarks is my first attempt
in this direction (specially for VS 2010). Please provide your feedback and suggestions. Don't forget to download and rate my extension on Visual Studio Gallery.

Share

About the Author

Manoj Kumar is a Humble Programmer and a trainer having dozens of trainings, publications and articles to his wallet.

His programming adventures began with Basic at an age of 11. Being a mathematician at core, soon he started looking for some more and moved to assembly language, later to C, C++, VC++ and finally to .Net.

He started his professional career as a VC++ 6 trainer, moved to embedded systems and device driver development, then to complex biological systems and finally moved to pure application development.

He has been teaching and training people for more than 12 years on a wide range of topics including Mathematics, Algorithms, Data Structures, C, C++, VC++, MFC, C#, Design Patterns and now a days he is working extensively with Visual Studio and .Net framework which includes VSX, WPF, Silverlight, WCF, WF, XAML and RIAs.