How does language syntax inheritance work?

Hey! I’m trying to see if I can make Nova my regular editor, and one of the missing pieces is a language syntax for MDX. MDX is like a hybrid of both Markdown and JSX that’s become very popular recently.

I think I need to create an extension that adds a language syntax for it. I’ve never made a syntax before, but I took a look at the syntax someone made for Atom as well as VSCode, and they’re pretty small which makes this seem doable.

However, when I started reading through the Nova docs I couldn’t find anything that explained how inheritance worked. I see the spot that talks about the syntax “parent”, but this seems to not be used for parsing?

When I open up the Nova.app package and look through the contents I can see the built-in extensions. I thought looking at TypeScript and JavaScript would be a good example, since I figured TypeScript must inherit from JavaScript, but optically they seem to be almost identical. Like someone duplicated the JavaScript syntax and just changed a few things?

But then I looked at JSX, which I also would assume inherits from JavaScript, and that syntax is like 20% the size of the JavaScript one. So it seems like it must be inheriting from JavaScript somehow?

I feel like I must be missing something here but I’m not sure what. Could someone explain or point me in the right direction? :pray:t3:

Thanks!

As you discovered already, Nova has no concept of syntax inheritance: the parent element is used for identifying syntax families, thus allowing use of syntax specific features.

What Nova has is the notion of syntax injection. There are two ways to achieve this, only the first of which is documented.

  1. Template scopes: these essentially add their contents to any scope from a syntax (including external syntaxes included in it). By “any”, I mean “any”: unless your template scopes are also valid inside things like strings, comments etc., or their parsing rules are strictly context-free (not a thing in most programming languages), you will probably get template scope highlighting where you rather wouldn’t. This is the blunt instrument of injection; Nova uses it for HTML+ERB, Smarty, and PHP-HTML.
  2. Override collections: you can indicate that a collection should override a collection of the same name included in the parent scope by adding the undocumented override="true" attribute. Include your modified scopes before the original scopes in the override collection and Björn Stronginthearm is your uncle. This is the scalpel of injection; Nova uses it for CSS, JSX, LESS, Sass, SCSS, and TSX. A good way to get a grip on the basic concept is the LESS extension. It pulls from the CSS extension and has the advantage of being far more terse than the JS based grammars.

Note that including collections from a builtin extension leaves your extension at risk of breaking on any Nova update, as Panic are entirely free to change their names. The alternative is to clone the base syntax into your extension and to update it whenever it changes in Nova; your users might experience a period of feature drift between a Nova update and an extension update, but overall, I would say this is probably the better UX.

2 Likes

Thanks! That was super helpful.

It seems like the overrides is what I want to do… given how small this is I don’t think duplicating the syntax makes sense. But I see your point about the breaking changes. I think ideally this should be included, given that it’s basically 99% markdown with just like two or three includes. Maybe if I just write it up and get it working Panic will include it :sweat_smile::crossed_fingers:t3: