Supporting the Navigation Bar (Managed Package Framework)
The Navigation bar across the top of the editor view provides quick navigation to any type or member in the file shown in that view. The Navigation bar is called a drop-down bar and holds two combo-boxes, one for types and the other for members. When the user selects a type, the view is updated to put the caret on the first line of that type while selecting a member updates the view to put the caret on that member's definition. In addition, as the caret is moved through the view, the combo-boxes are automatically updated to reflect the current location of the caret, providing feedback to the user about the location in the file.
Displaying and Updating a Drop-down bar
You must derive a class from the TypeAndMemberDropdownBars class and implement the OnSynchronizeDropdowns method. It is through this one method that you populate and maintain the state of the lists that are used to update the combo-boxes on the Navigation bar.
When your language service is given a code window, the base LanguageService class instantiates the CodeWindowManager object to hold the IVsCodeWindow object representing the code window. The CodeWindowManager object is then given a new IVsTextView object. It is at this time the CreateDropDownHelper method on the LanguageService class is called to obtain a TypeAndMemberDropdownBars object. If you return an instance of your TypeAndMemberDropdownBars class, the CodeWindowManager calls your OnSynchronizeDropdowns method to populate the internal lists and then passes your TypeAndMemberDropdownBars object to Visual Studio's drop-down bar manager. The drop-down bar manager, in turn, calls the SetDropdownBar method on your TypeAndMemberDropdownBars object to establish the IVsDropdownBar object that holds the two combo-boxes.
After the Navigation bar is in place, the OnIdle method of the base LanguageService class calls the OnCaretMoved method on the LanguageService class if the caret has moved. The base OnCaretMoved method calls the OnSynchronizeDropdowns method in your TypeAndMemberDropdownBars class to update the state of the Navigation bar. You pass an ArrayList of DropDownMember objects to this method. Each object represents an entry in the combo box.
What is Typically Displayed in the Navigation Bar
Two lists represent the contents of the combo-boxes that appear on the Navigation bar and correspond to types and members. The list of types typically includes all types available in the current source file. The types as displayed typically include the complete namespace information. For example, here is a C# code fragment:
namespace MyLanguagePackage
{
public class MyLanguageService
{
internal struct Tokens
{
int tokenID;
}
private Tokens[] tokens;
private string serviceName;
}
}
This would produce two types: MyLanguagePackage.MyLanguageService and MyLanguagePackage.MyLanguageService.Tokens.
The members list typically changes depending on what is visible in the types list and reflects the members that are available in that type. For example, using the code example above, if the types list shows MyLanguagePackage.MyLanguageService, the members list would contain tokens and serviceName.
Another feature of the members list that can be implemented is marking in bold the name of the member within which the caret is positioned. For example, again using the code example above, if the caret was positioned on the line int tokenID, the name tokenID could be displayed in bold in the members list.
Members can also be displayed in grayed out text, indicating they are not within the scope where the caret is currently positioned.
What you put in the types and members lists and how you display those elements is entirely up to you. The function of the Navigation bar is for quick navigation, so keep that in mind as you decide what is to be shown in the lists.
Enabling Support for the Navigation Bar
The MPF classes automatically support the Navigation bar, all you need to do is implement and provide the TypeAndMemberDropdownBars object in the CreateDropDownHelper method on the LanguageService class.
A registry entry called ShowDropdownBarOption determines if the Navigation bar check box in the Options dialog box is enabled and determines the initial state of the check box. The value for this registry entry can be accessed through the ShowNavigationBar property on the LanguagePreferences class. The registry entry directly affects the Navigation bar check box in the General language options page in the Options dialog box. If the registry entry is not present, the Navigation bar option is disabled and not available. Otherwise, the registry entry indicates whether the Navigation bar check box is initially selected or not. This means the ShowDropdownBarOption registry entry must be set when your language service is installed. This can be done with a named parameter on the ProvideLanguageServiceAttribute attribute.
The MPF classes themselves do not look at the ShowDropdownBarOption registry entry -- that is up to the individual language service. In your implementation of the CreateDropDownHelper method, you can look at the ShowNavigationBar property on the LanguagePreferences class and decide whether to return a TypeAndMemberDropdownBars object. If you do not return the object, the Navigation bar is not shown. Remember that the ShowDropdownBarOption registry entry can be changed by the user through the Options dialog box so your language service should pay attention to the ShowNavigationBar property.
Note that by using the ShowNavigationBar property in the CreateDropDownHelper method to selectively create the TypeAndMemberDropdownBars object, the editor window that contains the file type supported by your language service must be closed then reopened to show or hide the Navigation bar.
Implementing Support for the Navigation Bar
The OnSynchronizeDropdowns method is passed, among other things, two lists (one for each combo-box) and two values representing the current selection in each list. The lists and the selection values can be updated, in which case, the OnSynchronizeDropdowns method must return true to indicate the lists have changed.
You need a list of all types in the source file to fill in the types list and you need a list of members for each type. As the selection in the combo-box representing types changes, the members list must be updated to reflect the new type. What is shown in the members list can be either:
The list of members for the current type.
All the members available in the source file, but with all members not in the current type displayed in grayed-out text. The user can still select the grayed-out members, so they can be used for quick navigation, but the color indicates that they are not part of the currently selected type.
The lists of types and members are obtained from the source file through the ParseSource method parser. How you do this is entirely up to you. A typical approach is to start a parse with the parse reason of Check. This parsing operation can fill a Declarations object with all the declarations in the file, both types and members. This Declarations object is returned through the GetDeclarations method in the AuthoringScope class that is returned from the ParseSource method. The OnSynchronizeDropdowns method then scans each declaration in the Declarations object to find all the types and members and either populates or updates the types and members lists that are passed to the OnSynchronizeDropdowns method.
An implementation of the OnSynchronizeDropdowns method typically performs the following steps:
Get a list of current declarations for the source file.
How you get the declarations from the source file is entirely up to you. One approach is to create a custom method on your version of the LanguageService class that calls the ParseSource method with a custom parse reason that returns a list of all declarations. Another approach might be to call the ParseSource method directly from the OnSynchronizeDropdowns method with the custom parse reason. A third approach might be to cache the results of the last full parsing operation in the LanguageService class and retrieve that from the OnSynchronizeDropdowns method, thereby avoiding unnecessary parsing.
Populate or update the list of types.
The types list reflects what is currently displayed in the Types combo-box. The contents of the types list typically needs to be updated when the source has changed or if you have chosen to change the text styling of the types based on the current caret position. Note that this position is passed to the OnSynchronizeDropdowns method.
Determine the type to select in the types list based on the current caret position.
A typical approach is to search the declarations that were obtained in step 1 to find the type that encloses the current caret position, and then search the types list for that type to determine its index into the types list.
Populate or update the list of members based on the selected type.
The members list reflects what is currently displayed in the Members combo-box. The contents of the members list typically needs to be updated if the source has changed or if you are displaying only the members of the selected type and the selected type has changed. If you choose to display all the members in the source file, then the text styling of each member in the list needs to be updated if the currently selected type has changed.
Determine the member to select in the members list based on the current caret position.
A typical approach is to search the declarations that were obtained in step 1 to find the member that contains the current caret position then search the members list for that member to determine its index into the member list.
Return true if any changes have been made to the lists or the selections in either list.