dev, computing and games

I made a Visual Studio extension for 65C816 syntax highlighting. Suitable for SNES or C256 Foenix projects. Looks like this:

The source+binary are on GitHub, here: https://github.com/clandrew/vscolorize65c816

I had some past experience working on a big existing system in a Visual Studio extension in a job I had a little while ago. This was my first time writing an extension for a recent version from scratch. The experience was different.

I accomplished what I set out to do and it's working well, but the path of getting there was cumbersome.

Here is a list of the problems I ran into.


Problem: No extension template.

Root cause: Template requires an optional add-on.

How debugged: search engine.


Problem: Official template has build error right out of the gate:
error VSSDK1048: Error trying to read the VSIX manifest file "obj\Debug\extension.vsixmanifest". Could not load file or assembly 'Microsoft.VisualStudio.Threading, Version=16.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

Root cause: The template has a missing dependency. To fix, you have to update the NuGet package "Microsoft.VSSDK.BuildTools".

How debugged: Shotgun debugging

Build error
How to fix: right click project, go to NuGet Package Manager, and update Microsoft.VSSDK.BuildTools

Problem: Template does not behave correctly out of the gate. When launched with debugger, it is never loaded

Root cause: The template is missing an attribute

How debugged: Search engine

Add the highlighted lines to fix

You don't create a syntax highlighter object out of the blue. You define a 'Package' object to set it up. I defined one in code.

Problem: Package object is never instantiated.

Root cause: You have to define, at a minimum, two attributes to make it get loaded: [PackageRegistration] and [ProvideAutoLoad].

How debugged: Looking at other extensions as examples + shotgun debugging

Add something like the highlighted lines or else the package won't get instantiated.

Problem: The package is instantiated, but doesn't correctly associate with the intended files. Error message when opening files that have ProvideLanguageExtension. "An error occured in 'file' when attempting to open 'file.s'. There is no editor available for 'file.s'. Make sure the application for the file type (.s) is installed."

Root cause: Something is stale. Reload the file! Even if you restart the whole IDE, it's not enough! It stays stale unless you specifically reload the file

How debugged: Shotgun debugging


Problem: The package adds a LanguageInfo service, but the LanguageInfo is never instantiated.

Root cause: You have to call AddService after InitializeAsync, not before

How debugged: Shotgun debugging + code examples

It should be ordered like this.

Problem: The syntax is not highlighted as intended

Root cause: Need to have IVsColorizer::ColorizeLine return the intended values

How debugged: Actual debugging


Problem: The choice of which attribute value maps to which color seems arbitrary

Root cause: There is a system of default colors. You can know that default through experimentation, or over-ride it in a specific way

How debugged: Experimentation


Takeaway- I accomplished what I was trying to do, but there was a lot of shotgun debugging and 'just trying things'. The extension framework has you hook into a complicated system which is also closed-off and opaque, so there is no way to directly debug it.

It may as well be running on a different computer.

By "system", I'm referring to

  • The mechanism that instantiates your custom package and calls InitializeAsync on it
  • The thing that looks at your custom package and loads it based on PackageRegistration
  • The thing that sets up associations using package attributes like ProvideLanguageExtension.
  • The thing that executes methods of objects set up with IServiceContainer::AddService

Look at my extension or other people's samples. You're supposed to define a **** ton of attributes on the Package class.

Here's an example from an open source project on GitHub:

^attributes above the class definition.

It's to the point where the attributes are really a language in and of themselves. Guess what, they feed into some complicated loader that executes before your code is ever executed. If there's a mistake in the attributes? Get your psychic powers ready. Because there's no insight into this closed system or way see what it's doing. It doesn't even have the decency to put its state to debug console.

The time I spent actually debugging problems was in the minority. Most problems, the only way to fix them was by trying things. It's really bad. They could not be debugged in the debugger because they occur in a complicated system you don't have access to. This system and its higher-level concepts were not documented well enough to automatically know what to type. "Is ProvideLanguageService not supposed to have languageResourceID 100? Is that the reason it's failing, or something else?"

I'm not even angry about the 17KB of code and god knows how much memory it takes to make text a different color according to a simple scheme. What's really bad is this this closed off system that everyone is supposed to just be okay with, since I see it as part of a trend. UI layouts from markup are like this. App store launching is like this. Software junk food "press F5"-style emulators and VMs are like this. As we get more and more complicated software systems, there isn't enough follow-through to make them fully open and debuggable. So if they 'just work the first time' fantastic. But we all live in reality. Things will go wrong, and when they do, it's clear the system is grand and complicated and undocumented and undiagnosable.

As a random example, can you imagine if Direct3D 12 had no debug layer, and all it told you was E_INVALIDARG? Why would we accept this more broadly?

This is why I keep writing GUI applications as executables with Win32 or Windows Forms say. The form designer has the decency to give you a call stack. Actionable stack or some error string should be the bare minimum.

Shifting topics, here are things I learned about Visual Studio custom colorizers:

  • The 'state' values passed to/from your callbacks have whatever meaning you want them to. The meaning of states is all user-defined. It's opaque to Visual Studio.
  • An IVsLanguageInfo is reponsible for providing two things: a colorizer, and a code window manager. But you don't need to have both. You can just provide one if you want. For example you can provide a colorizer, but return E_NOTIMPL to IVsLanguageInfo::GetCodeWindowManager.
  • In IVsColorizer::ColorizeLine, character at index N in the 'pszText' corresponds to element N of 'attributes' parameter.
  • To add colors to text, you set values in the 'attributes' parameter of IVsColorizer::ColorizeLine. Like this:

Why does 1 mean red? Because it's based on whatever you set in your IVsProvideColorableItems callback object.

Yes, it's 1-indexed.

If you don't have an IVsProvideColorableItems set up, you'll get some defaults with blue, green, red, and black (default) text numbered in some way. You can experiment what means what. Using a value of greater than 6 will crash the extension, so that's fun. In my case, the default was almost good enough, but I wanted gray for directives like C++ has, for e.g., #include and #pragma. So I did end up implementing IVsProvideColorableItems.

All told, if you use the syntax highlighter with custom build tools, it looks like this:

so the experience is pretty smooth.

Direct download to the release is here https://github.com/clandrew/vscolorize65c816/releases/tag/v1.0.

April 25th, 2022 at 4:12 am