ProvideLanguageServiceAttribute class has been with us for many years now. At least since it shipped as source code in the Visual Studio 2005 SDK. With more than two dozen properties, it’s easy to get lost trying to figure out which ones are meaningful and/or useful, and which ones are safe to ignore.
Like the other registration attributes, for Visual Studio 2010+ extension developers this class aids in the creation of a .pkgdef file for a VSPackage extension. Each property is associated with one or more entries in the file, and eventually corresponds to registry keys which can be queried by either Visual Studio itself or an extension. In the case of ProvideLanguageServiceAttribute, the supported entries fall into the following categories.
The following properties determine whether certain standard UI elements in the language options pages are visible and/or enabled.
EnableAdvancedMembersOption. When set to false, the Hide advanced members check box on the General language options page is disabled. The default value is false. This option is closely related to both the ShowCompletion and HideAdvancedMembersByDefault options described below.EnableLineNumbers. When set to false, the Line numbers check box on the General language options page is disabled. The default value is true.ShowCompletion. When set to false, all of the check boxes under Statement completion on the General language options page are disabled. The default value is false.ShowDropDownOptions. When set to false, the Navigation bar check box on the General language options page is disabled. The default value is false. Note: This behavior was observed in Visual Studio 2013, and differs substantially from the behavior described on MSDN. Users implementing this feature should set the property to true, and other users should leave the property unset for consistent behavior that matches MSDN.ShowSmartIndent. When set to false, the Smart radio box on the Tabs language options page is disabled. The default value is false.The following properties determine the default values of language options.
DefaultToInsertSpaces. When set to true, the Insert spaces option on the Tabs language options page is selected by default. When set to false, the Keep tabs option is selected by default. The default value is false.DefaultToNonHotURLs.HideAdvancedMembersByDefault. When set to true, the Hide advanced members box is checked by default on the General language options page. The default value is false.The following properties control standard editor behavior which is not exposed through the standard language options pages.
RequestStockColors. For MPF language services targeting versions of Visual Studio prior to 2010, this was a very important property for users implementing custom syntax highlighting items. When set to false, Visual Studio will try to cast your implementation of IVsLanguageInfo to an instance of IVsProvideColorableItems to get information about the additional syntax highlighting colors. For Visual Studio 2010+ extensions, a much cleaner API is available by simply setting the property to true and exporting instances of ClassificationTypeDefinition and EditorFormatDefinition to support additional syntax highlighting colors. The default value is false.SingleCodeWindowOnly. When set to true, the Window → New Window command is disabled for editor windows created for this language. Note that this does not affect the behavior of the Window → Split command. The default value is false (and should not be changed unless absolutely necessary).Many of the available properties set registry keys that do not directly impact Visual Studio at all. For users basing their language service implementations on the managed package framework (MPF), these properties control behavior provided by that framework. Since I no longer use this framework for language service implementations, I’ll omit the detailed descriptions of these properties.
AutoOutliningCodeSenseCodeSenseDelayEnableAsyncCompletionEnableCommentingEnableFormatSelectionMatchBracesMatchBracesAtCaretMaxErrorMessagesQuickInfoShowMatchingBrace
Install the Rackspace.KeyReporting NuGet package for improved error reporting when a strong name signing file is missing from the build computer.
For the open source projects I work with on a daily basis, the easiest way to ensure strong name keys are not accidentally included in source control is storing the keys outside the directory structure of the project. For new users of the project, this often means a freshly downloaded copy of the project’s source code will not compile, and the error message resembles the following:
error CS7027: Error signing output with public key from file ‘..\..\..\..\..\..\keys\antlr\antlr-net45.snk’ — File not found.
My current approach to resolving this centers around clearly stating the manner in which this problem can be resolved. For example, the following error message immediately informs the user of the necessary step.
error : This project references a strong name key that is missing on this computer. Run ‘sn -k D:\github\keys\antlr\antlr-net45.snk’ to generate the file.
This feature was reasonably easy to provide by creating a custom KeyReporting.targets file, and including it in the project. However, to make it even easier it is now available for installation as an open-source NuGet package. Simply use NuGet to install the Rackspace.KeyReporting NuGet package and your build will report these improved error messages.
A complete example of the steps required to implement this feature is available in rackerlabs/dotnet-threading#46.
As every package owner should know, changing the strong name of an assembly is a breaking change. One component of the strong name is the signing key, which is responsible for the PublicKeyToken component of the final strong name. For most of the open-source projects I contribute to, the strong name used for “official” releases is not publicly distributed. However, as described above, developers are encouraged to create their own *.snk files in order to build local copies of the project for testing and ongoing development. To make sure the assemblies which are published to NuGet use the correct strong name keys, the build script was updated to only create NuGet packages when the strong name key of the assemblies it contains match the expected values.
Create a separate PowerShell script responsible for checking the strong name of an assembly. Since the use reflection to load the assembly directly would prevent the file from being updated without restarting PowerShell, this separate script is used to execute the check in a separate process.
The next step is defining the expected public key token for the assemblies. Note: to determine the public key token for the first time, use 'placeholder' originally and observe the error message produced by the build at the end of this post.
# Note: this value may only change during major release
$ExpectedPublicKeyToken = '8b3790928cb57ea0'
The actual key checking is straightforward. I prefer to include a parameter to the build script called $SkipKeyCheck which allows developers to selectively disable the key check.
# By default, do not create a NuGet package unless the expected strong name key files were used
function Resolve-FullPath() {
param([string]$Path)
[System.IO.Path]::GetFullPath((Join-Path (pwd) $Path))
}
if (-not $SkipKeyCheck) {
$assembly = Resolve-FullPath -Path "..\Project\bin\$BuildConfig\AssemblyName.dll"
# Run the actual check in a separate process or the current process will keep the assembly file locked
powershell -Command ".\check-key.ps1 -Assembly '$assembly' -ExpectedKey '$ExpectedPublicKeyToken' -Build 'AssemblyName'"
if ($LASTEXITCODE -ne 0) {
Exit $p.ExitCode
}
}
]]>FileExtensionToContentTypeDefinition. This post is all about why I no longer use/export this class, and what I now do instead. The steps in this post only associate specific, known extension(s) with a content type. Further integration into the user-customizable File Types settings and Open With… features requires additional work described in my answer to this Stack Overflow question: Supporting user-specified file extensions in custom Visual Studio language service.
The following requirements led me away from this attribute.
IVsCodeWindowManager (beyond the scope of this article).IVsCodeWindowManager, you must provide an implementation of IVsLanguageInfo.IVsLanguageInfo, the GetFileExtensions and GetLanguageName methods of that interface will be used instead of the values given to FileExtensionToContentTypeDefinition.To maximize your ability to support the full range of Visual Studio features in your extension, you can take the following steps to register your new language or file type.
Package.
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#111", "1.0")]
[Guid("your guid here")]
public class ExamplePackage : Package
{
}
IVsLanguageInfo.This is actually pretty simple.
IVsLanguageInfo implementationRegister the IVsLanguageInfo implementation using the ProvideLanguageServiceAttribute and ProvideLanguageExtensionAttribute registration attributes. Override the Package.Initialize method to provide the implementation. Here is the updated ExamplePackage class.
ContentTypeDefinition.NOTE: Since we are not using a custom editor factory, the content type name used here must match the language name used as the 2nd argument to ProvideLanguageExtensionAttribute above.
In this post, I’ll talk a bit about the current state of the extension and the features currently supported by the debugger.
Current features of the debugger include:
In our opinion, this is hands-down the coolest feature of our debugger.
We’ve been looking, but so far it seems that no current Java IDE can properly distinguish between multiple statements on a single line. We aim to change that. The image below shows a series of steps in the debugger (the Step Over command was executed exactly one time between each image). As you can see, our debugger follows the logical stepping sequence from the initializer to the condition of the for statement.
Here is an animated image showing a more complete example.
The debugger provides special handling for arrays and classes implementing Collection, Map, and a few other interfaces. To ensure that the debugger doesn’t restrict access to information you need, a special Raw Values property is provided which the user can expand to see the object’s true internal structure. Direct access to the Static Members of classes is provided as well (standard for all classes).
Curious about what your code is compiling to? The disassembly window fully supports Java bytecode while debugging your program.
The Java debugger extends the Debug → Exceptions window with support for Java Runtime Environment exceptions.
Handling behavior for exceptions may be configured by package or by individual exception type. The following example shows a user configuration requesting the debugger stop immediately when an IllegalArgumentException is thrown.
When an exception is thrown, the debugger first checks to see if the user has requested the debugger stop when the exception is thrown. In this case, we configured the debugger to stop when an IllegalArgumentException is thrown, whether or not a catch statement is available to handle the exception.
If an exception is thrown and no exception handler is available to handle the exception, the debugger stops immediately at the point the unhandled exception is thrown. The user does not need to specially configure the debugger to respond to this error case.
The following image shows the contents of many (but not all) of the currently supported debugger windows in Visual Studio.
]]>This extension integrates a modified version of the standalone ZGRViewer into the NetBeans IDE. When a GraphViz file (*.dot or *.gv) file is opened, a plain text editor is presented along with “Source” and “Visual” buttons on the document’s tool bar. When the document is saved, the plugin uses GraphViz to generate an SVG for the file which can be viewed by clicking the “Visual” button. Further changes to the graph may be made by switching back to the “Source” view. Note: if changes are made to the source file, the visual graph is only updated after the file is saved.
Here is an example from editing Block.dot in the ANTLR 4 runtime documentation. The “Visual” pane for this file is shown above.
This extension requires the following to be installed separately.
After installation, if the dot executable is not in your system path, you’ll need to configure the path to dot before using the plugin. In the editor options under the Miscellaneous section is a GraphViz tab. Here is an example configuration on the Directories tab:
You may also wish to enable anti-aliasing on the Visualizer tab:
In the past I alluded to spending a great deal of time thinking about ways to improve the performance and usefulness of a code completion feature. This algorithm is complicated but ends up producing consistent, predictable behavior. It truly excels with COMPLETION_AUTO_POPUP_DELAY set to 0 and low-latency implementations of AsyncCompletionTask.query, but still performs better than alternatives when latencies are observable.
To start with, a couple definitions from the subject line:
I noticed that Ctrl+Enter deletes the current identifier before calling CompletionItem.defaultAction. Iāll refer to the behavior of Ctrl+Enter as Extend because it extends the completion to the end of the current identifier. Iāll refer to the behavior of Enter as No-Extend.
For this post, Iām examining the following questions:
As part of my continued work on ANTLRWorks 2, I have modified the Editor Code Completion module to support everything described in this email without any breaking API changes relative to the current specification 1.28. In addition to preserving API compatibility, my current implementation exactly follows the existing code completion behavior if a developer does not explicitly override it. The changes are available with patches (unfortunately multiple patches as I tweaked a few things) as an RFE in Bug 204867 in the NetBeans Bugzilla.
The difference is present because the current algorithm cannot reliably answer question #2.
From what I can tell, the code completion algorithm uses this feature to compensate for not keeping track of information available when the completion was invoked. For example, suppose you are trying to complete the identifier getStuff, and the following currently present. For reference, assume this is columns 0 before the āgā through 5 after the ātā.
getSt
When code completion (Ctrl+Space) is invoked at positions p=0 or p=5, the user expects the behavior of Enter. When the code completion is invoked at positions 0<p<5, the user expects the behavior of Ctrl+Enter. Also note that at position p=5, the algorithms of Enter and Ctrl+Enter are equivalent.
Using the example from Part 2, itās clear that the current selection algorithm used in the completion dropdown does not properly handle the Extend behavior, because it only considers the text before the caret when selecting an item. When the completion algorithm in invoked in Extend mode, all of the text of the current identifier should be considered when choosing the default selection.
If the code selection algorithm of Part 3 is implemented, then the user will encounter major problems under the current filtering algorithm. In addition, it should be immediately apparent that the current filtering algorithm is crippled because it is fully incapable of handling even the simplest misspellings when completion is invoked at the end of an identifier (position p=5 in the previous example). While I do not believe the following rules are ideal for all situations, I designed them to be easily implemented and feel similar to the current rules while preserving the ability to support the selection mode of Part 3 as well as handling many misspelling cases.
In No-Extend mode with no misspellings before the caret, these rules produce exactly the same result as the current implementation. In No-Extend mode with misspellings present before the caret, these rules prevent having an empty (useless) dropdown appear. Unfortunately, if the user attempting to complete getShell types getSt and presses Ctrl+Enter, the filtering above would result in only showing getStuff. The solution is adding the following rule which has much larger ramifications.
To allow even more convenient typing, the filtering algorithm can be updated to also allow the following.
Currently instant substitution only operates if the filtered list has a single item in it. It also only works if the caret is located at the end of an identifier, and when the prefix is a case-sensitive match. The current algorithm is in CompletionImpl.requestShowCompletionPane. This algorithm would need to be updated as follows.
It should be clear that if the filtering rules are relaxed per the advanced rules in Part 4 (especially Part 4.1), the current selection algorithm of first prefix match will do a poor job of choosing items the user is trying to complete. The following selection rules are prioritized for ideal behavior, but an implementation may use variations for efficiency as long as the variations result in predictable behavior (typically restricted to performance related simplifications to rules 1 and 9).
Unless otherwise specified, character matches are case-insensitive. While the user has an option to explicitly disable case-insensitive matching, if all of the rules from this email are in place then that option will negatively impact code completion usefulness.
If semantics are tracked as well as the actual inserted text (e.g. for Java an MRU list of ElementHandle instead of String), then a weighting algorithm should be used to balance between text inserted more recently and having a full semantic match. One possible way to handle this is having one MRU that tracks semantic elements and one that tracks strings. In the semantic element list make sure there are never 2 items present which are valid within the same context, then always prefer a semantic match over a plain string match. The net impact of considering semantics is very cool, but hard to explain the nuances (the end user will just see it as the algorithm always knowing what to type).
In theory, a weighting algorithm could also be used to provide a hybrid of the Validity and Recently Used selection steps. At this point I have not considered the specifics of such a feature.
The C# language service Visual Studio 2010 implements several items described in this post. For users with access to Visual Studio 2010 with a C# project, the following is a list of some of the differences between it and the algorithms above.
Visual Studio 11 apparently includes an additional fuzzy logic selection feature, which Iām really hoping they properly inserted between the Word Boundary and Validity steps of the selection algorithm (fingers crossed).
]]>The original goal of this project was creating an MEF service that allowed exporting classes implementing IToolWindowProvider, and have the service manage the creation of the tool windows and their entries on the View > Other Windows menu. This worked, but had several drawbacks that eventually led to the conclusion that this was the wrong approach. First and foremost, this method forced the assemblies providing tool windows to load even when the tool windows werenāt visible. The performance implications of this rule out using MEF as a general solution to this problem. That said, here are some other ālittle thingsā that I didnāt have worked out in the MEF solution:
Here is an outline of the general process of creating a new tool window. Following the outline, Iāll explain in detail what each step requires.
Iām basing this off of the VSIX project, because itās a nice clean project template. Itās also beneficial because users that already have a VSIX project can easily add a tool window to it.
For this part, you can create any control derived directly or indirectly from System.Windows.Control.
This helper class handles most of the code required for providing a tool window. You can download WpfToolWindowPane.cs here.
The last step is adding a command to the View > Other Windows menu so your tool window can be opened.
Open the MyProject.vsct file and fill it with the following. Use the same Guid for guidMyProjectPackage as you used for the MyProjectPackage class. For the other two placeholders, create new Guids.
< ?xml version="1.0" encoding="utf-8" ?>
Add a class named Constants to your project to make the Guids you used in the command table accessible to your code.
internal static class Constants
{
public const int ToolWindowCommandId = 0x2001;
public const string MyProjectPackageCmdSet = "{00000000-0000-0000-0000-000000000000}";
public static readonly Guid GuidMyProjectPackageCmdSet = new Guid(MyProjectPackageCmdSet);
}
Open the MyProjectPackage class. Add a new attribute to provide your tool window: [ProvideToolWindow(typeof(ToolNameToolWindowPane))]. Add the following code to the body of the class:
protected override void Initialize()
{
base.Initialize();
WpfToolWindowPane.ProvideToolWindowCommand(this, Constants.GuidMyProjectPackageCmdSet, Constants.ToolWindowCommandId);
}
]]>I’ll start with a couple fundamental concepts. First, there are three key things that get parsed in an IDE:
Second, the very nature of writing code results in semantically incorrect (invalid) code *almost always*, and syntactically incorrect code most of the time. Very often, the code is in a state that it can’t even be lexed by the language’s lexer. It is critical for each of the above cases to absolutely minimize the impact on the ability to provide meaningful coding assistance. This point is the fundamental reason I believe incremental lexers and parsers are significantly less valuable to an IDE than is often believed.
Here are several things to remember about each of the three "parsables".
Syntax highlighting is the only item above that requires a real-time component. The syntax highlighter must be able to perform a standard view update in less than 20ms for any character input. The easiest way to accomplish this is writing a "lightweight" lexer that, given any input that starts at a token boundary can tokenize the remaining text. The backing engine for my current syntax highlighters maintains a list of lines that do not start with a token boundary – for many languages this occurs for the 2nd and following lines of a multi-line comment. I can start lexing at any line in the entire document as long as it isn’t contained in this list, and can stop under a similar condition (the stop condition is slightly more complicated so I’ll leave it out). *The primary syntax highlighter must not perform any form of semantic analysis of the result.* A secondary highlighting component can asynchronously add highlighting to semantic elements such as semantic definitions, references, or other names.
Here are some basic things I do to improve the performance of the syntax highlighter’s lexer:
For any block of language code that does not affect other open documents (such as the body of functions in C), don’t validate the contents of a block. For C, this could mean parsing the body of a method as:
body:block
block:'{' (~('{'|'}') | block)* '}';
Often, all you care about are the declarations and definition headers. This sort of "loose" parsing prevents most syntax errors in the body of methods from impacting the availability of the key information – references to the declarations are usable at other points in code. Further improvements can be made by forcing a block termination when a keyword is found that cannot appear in the block, but since this can cause some unexpected results and offers relatively low "bang for the buck", I recommend holding off on this approach.
One of the most difficult aspects of an "intelligent" IDE is how to handle files while they are being edited. In designing an appropriate attack on the problem, it’s important to identify the types of information you can gather and for each: 1) Categorize it, 2) Give it a difficulty rating, 3) What can you do with it, and 4) Prioritize it. I’ll give several examples along with how you might leverage each to improve the overall usefulness of the IDE. Note that all of the below are performed asynchronously using a parameterized deferred action strategy described in a section below.
I decided to address this separately from the above. Here are the governing factors an auto-complete feature:
Core strategy: This is not complete, but gives a general idea of the initial approach that gives quite tolerable results.
Start at the cursor and read tokens in reverse "until they no longer affect the current location". For c-like languages, this means reading identifiers, periods, arrows (->), and parenthesis with arbitrary contents and nesting.
The result can generally be parsed as a postfix expression; the parser should be able to built an AST for just that postfix expression without any additional context.
Evaluate the AST against its previously cached context – use the buffer-mapped span of the enclosing language elements to evaluate the visibility of elements as you manually walk the expression’s AST. At each step, generate a list of visible elements, and select the appropriate item before continuing. At the end, you’ll have a list of accessible items at the point auto-complete was triggered – match that against any text the user has already typed and either fill in the single result or present a dropdown.
(only one example here so far)
Due to the way the unopened file parser skips parsing function bodies, it tends to be extremely fast. When it fails to parse a document, due to say a syntactically incorrect field declaration, you don’t want your opened files to stop showing navigation information. Upon failure, the opened file strategy can fall back to using an ANTLR fragment parser to locate the headers of language elements (class, field, or function definitions), from that information you can infer the scope of each element, and generally identify most or all of the items declared in a document.
After identifying elements by name and their "span" in the document, you’ll want to keep a list of the information around in case future edits render both of the above parsing methods unusable. When the header portion of a language element is deleted as part of a document edit, the element can be immediately removed from the cached list of elements located in the current file. Further, if the previous element had an inferred termination due to mismatched braces, its span can immediately be expanded to include any remaining portion of the original span of the removed language element. The cache relies on [buffer span mapping] to properly track the current location of each language element as edits are applied to the document.
Any time the IDE tries to offer information about a language element in an opened file, the information is checked against that documents cache for potential updates (for example, Go To Definition should always go to the correct location, even if a large block of text was pasted above the definition).
This strategy is basically a modified, asynchronous "dirty" flag based trigger for an arbitrary action. The strategy addresses several goals:
Here’s the basic implementation:
To use the application:
Here is the configuration application and the result:
]]>There are a couple goals here:
I’ll start with the usage (since that’s the interface people will normally see) and follow it with the implementation.
Now this is easy.
First you import the IOutputWindowService:
Then you use it to get an output window, which you can write to:
If you want to write to one of the standard panes, pass one of the following to TryGetPane:
IOutputWindowPane and IOutputWindowService interfacesIOleServiceProvider