10.2 Creating Custom PackagesIt is interesting to see how VS.NET uses packages, but since we can't change those packages, the most interesting thing we can do is create our own. In the Figures sample provided with the VSIP SDK, there are example packages that supply a design editor, a project package, and a language service. Once you obtain the VSIP license, this sample provides a good starting point if you decide to create your own packages. 10.2.1 ViewsA view is a window that presents a project item such as a source code document to the user and allows him to edit it. (For example, the visual form designer for Windows Forms is a view, as is the code editor.) In VS.NET a document can have any number of views, although most only have two: Design and Code. For each view there is an object that is responsible for drawing the proper representation of the document. This object is known as the DocView. The document itself is represented by a DocData object, which is responsible for persistence and storing common information about the document. Design views allow visual editing. In the most common scenario for building such an editor, you'd like to have a custom designer for some particular source file to allow visual editing and dragging and dropping of objects from the Toolbox, much like the built-in ASP.NET and Windows Forms design views. You can write such a visual editor in two ways:
If your project items are text files of some kind, you will want to supply a code view. However, you do not normally need to write this view yourself—the default VS.NET text editor (which is implemented as a package as well) will work fine. There is rarely a good reason to reinvent a text editor when VS.NET supplies one for you, so you can just reuse the existing "code" DocView rather than having to build your own.
The development environment keeps a table of documents that are currently open called the running document table or RDT. When a file is opened, the environment first checks the RDT to see if another editor already has the document open. If the file is not open, the environment asks the project package to open it. As already described, this finds the suitable editor package and uses its Editor Factory to open the correct DocView for the document being opened. By default, a design view will be opened if one is available, but the user can request a different view. The editor package controls the commands for switching between views and adds the commands necessary to support the exposed views. These may appear on the context menu or as tabs at the bottom of the editor pane, as they do in the HTML designer that you can see in Figure 10-2. Figure 10-2. HTML designer commandsWhen the Factory gets a request to open a DocView, it is passed a string that tells it which view type is being requested. The Factory is then responsible for creating the correct DocView object. This could mean returning the current view if the document is already open. The Factory might also return a custom editor object (in the case of a custom designer), or it might ask the environment to return an instance of the default text editor. After the Factory returns an interface pointer to the correct object, its work is complete, and from then on the editor object talks directly to the environment. Once you have built your package, you will need to add the necessary registry entries. Two entries come into play for an editor. The first is HKLM\Software\Microsoft\Visual Studio\7.1\Packages\PackageGUID. All packages require such a registry key, regardless of the services they provide. The PackageGUID is the CLSID for the coclass that implements the IVsPackage interface, which is the environment's entry point into the package for obtaining the necessary services. Remember, although packages are essentially COM components, they are not registered in the normal way. This entry takes the place of the normal COM registration, so it must also indicate where the package DLL resides. The PackageGUID key, therefore, has a string value called InprocServer32. This serves the same purpose as COM's InprocServer32 key in that it simply contains the path of the DLL (although for VS.NET packages, it's actually a registry value and not a key as it would be in COM). The other registry entry needed is under HKLM\Software\Microsoft\Visual Studio\7.1\Editors. Here you need to add the necessary entries to tell VS.NET what file extensions you want to be an editor for and what views you support (as well as provide a pointer to your package GUID). Once you have the registry entries taken care of, you can add a file to your project with the appropriate extension and VS.NET will load the editor package. During the initialization of the package, the package object needs to register the editor(s) with the environment. Once this is done, you can design and edit files with your new editor. The VSIP SDK Figures sample provides a designer for visually designing shapes that will appear on a Windows Forms application. See Figure 10-3. This sample uses a file with a .fig extension to persist the type and coordinates of different shapes. A separate .cs file is created by the project package when the project is built. This file is the source file that will be compiled by the C# compiler and will end up drawing the shapes on the form. Figure 10-3. The Figures project screenThere are a few things to notice about this project screen that help emphasize the depth of the integration you get when you build a package. You can see that the figure edit package has added a new tab to the Toolbox (FigPkg Sample) from which you can drag and drop the different figure objects onto the form designer. With a package you can also add command items to the context menu that appears when a user right-clicks on the form view. You can also see that the property window has specialized information about the .fig file. 10.2.2 Language ServicesIf you switch to the code view of the .fig file, you will see that there is both syntax coloring and IntelliSense (see Figure 10-4 ). Figure 10-4. Code view enhancementsAs with most designer editors, the figure editor package relies on the VS.NET default text editor for the code DocView. In order to enhance this editor to provide all the cool stuff we expect when editing code files in VS.NET (e.g., syntax coloring, IntelliSense, statement completion, method tips, error markers) with a new language, you must provide a language service package. Under the HKLM\SOFTWARE\Microsoft\VisualStudio\7.1\Languages\File Extensions key is a list of file extensions, each with a package GUID listed as the default value. This is the GUID of the language service for that particular file extension. This is not the package GUID—language services have their own GUID, which is typically not the same as the corresponding package GUID. VS.NET will locate the language service underneath the HKLM\SOFTWARE\Microsoft\VisualStudio\7.1\Languages\Language Services key by looking for a key whose default value has the appropriate GUID. (The keys underneath the Language Services key all have textual names like Basic or CSharp, but each of these keys has a default value that is the language service's GUID.) The language service key has a value called Package, which is the GUID of the package that provides the language service. The language service works with the text editor and coordinates with it to provide enhancements such as syntax coloring and IntelliSense. As you type in the text editor, the editor and the language service have a constant bidirectional communication going on. So as you type in the editor, the editor passes the text you are typing to the service, and if the word you are typing needs to be colorized, the service will tell the editor. If you press Ctrl-spacebar to invoke statement completion, the editor calls the language service, which gives the editor a list of items appropriate for the current context. Each different type of enhancement is implemented by providing an object that implements certain interfaces. When the document is being edited, the environment calls the appropriate interface for each enhancement, the interface pointers having been passed to the environment by the language service during initialization. (To obtain a list of these interfaces, you will need to become a VSIP licensee.) For example, as text is typed into the .fig file, the text will be passed to an object that is responsible for colorization. As each token is passed into that object, it returns a flag attached to each token that should be colorized. (So the colorization object is really a lexical parser that tells the editor which words are language keywords.) 10.2.3 New Project TypeEditing the file with all the "extras" is nice, but in the end the file is useless if it cannot be compiled as part of the build process. The .fig file has to be converted into a .cs file, so that when the project is compiled the correct shapes are drawn on the form. In order to be involved when the project is compiled, you need to create a project package (although in this case the .fig file could just have a custom tool associated with it—see Chapter 2). A project package is an object that implements a certain set of interfaces that allows it to interact with the IDE to coordinate project creation, persistence, and compilation. Unlike adding a New Project Wizard (which only allows you to create a custom set of project items for an existing project type), creating a new project type with a VSIP package give you total control over the whole project lifecycle. You may need to create a project type package in order to:
If you need custom project items but don't need this type of control, you are much better off creating a new Project Wizard (see Chapter 9). In the case of the Figures project, the most interesting thing it does differently than any of the other project types is to take the .fig file and use an internal parser to generate a separate .cs file from the .fig file syntax. It adds this file to the project and compiles it when the project runs the C# compiler. The project package architecture works much the same as other packages. The environment creates the package object, and passes in its interface pointer for the package to obtain services. The package object then registers its project factory interface with the environment. When a project that belongs to this package is opened (or created), the environment asks the factory to create (or hand back) an object that represents the project itself. If the configuration of the project changes, the project object is called and is responsible for persisting that information. If a build command is issued, the project object must do whatever is appropriate to build the solution. When a new file is added, the project object is responsible for persisting that file and putting it in the appropriate place. When the project is added to source control, the project object is responsible for checking items in and out through the source control services exposed by the environment. When a debug command is issued, the project object must work with a debugging package to start and manage the debugging process. If your compilation process generates machine code (generally x86) and you also output a .pdb file, there is a DE (debugging engine) for Windows code (that has an expression evaluator for C++), so you don't need to create any additional packages. Likewise, if your project builds a .NET component (i.e., it produces IL), you can use the IL debugger. If your project package implements a new language that doesn't compile into IL or x86 assembly, you need to create a new debugging engine package. In the case of the Figures project, since it is using C#, the project object can just use the existing DE built into VS.NET for IL. However, it is useful to augment this by building an expression evaluator (EE) to work with the DE. 10.2.4 Debugging Engines and Expression EvaluatorsIf you implement a new language that does not emit either windows native code or the corresponding debug format files (.pdb), you will need to write a debugging engine (DE). The VSIP SDK includes information and a sample to show you how to build a DE. A DE is a component that implements the services necessary to debug a particular architecture. (There are debugging engines for Windows code, IL, TSQL, and script built into VS.NET, so you need to provide a DE only if you are targeting some other architecture.) A DE works with an IDE (or the operating system) to provide execution control services (e.g., breakpoints and statement stepping). Whether you write a DE or not, you may also wish to provide an expression evaluator (EE). An EE is a VS.NET package that coordinates with the IDE to evaluate language expressions at runtime. This can happen in both the immediate and watch windows while a program is being debugged. When the VS.NET debugger loads and execution stops on a breakpoint, the DE in question creates an instance of the EE engine for the language in use and gives the EE a list of variables that need to be displayed in the locals window. The EE is responsible for parsing those variable (symbol) names and giving back to VS.NET the memory location of their values. A similar process happens when a symbol is requested from the watch window. When a statement is typed into the immediate window, however, the EE must both parse the symbols and possibly return a result. (For example, the immediate window allows the evaluation of a valid language expression such as "4+5,") The VSIP SDK comes with a sample called MyCEE. This EE will evaluate locals and expressions in the watch window for the MyC language (which is a language whose compiler is also supplied as an example in the SDK of how to implement a language that compiles to IL). |