Sublime Forum

Meaning of multiple scopes in syntax definitions


Hi, I’m curious about the meaning of multiple scope values in syntax definition files.

Firstly, consider this:

There are two meta scopes. Are they both just pushed onto the stack?

The second interesting place is here:

In this case the meta scopes apply exactly to the pattern. In the same fashion as ordinary scopes do. Am I right?

And the final example:

Here we see two ordinary scopes applied to the same pattern. What does it mean? Which one of them will define the style?


1 Like


Basically we distinguish two kinds of scopes.

  1. The meta. scopes are used to create logical units which provide semantical context for completions or plugins.
  2. storage, keyword, variable,punctuation … scopes which primarily target syntax highlighting.

About snippet 1 (Python)

The whole import statement is scoped meta.statement.import.python.

import package

from .foo import bar

from .foo import (bar as baz)

The second scope meta.import-list is used to scope only (bar as baz).

Stacking meta scopes like that is one way to create multiple layers of logical units. This is useful especially if such scopes like meta.import-lists are used in different contexts.

If only the import list is of interest a selector may look like meta.import-list, which targets all those regions no matter whether they belong to meta.statement.import or any other one.

If only certain regions are needed a selector can easily combine those by (meta.statement.import | meta.statement.whatever) & meta.import-list.

An alternative was to put everything into one scope name, which would then look like meta.statement.import.import-list. A real life example is meta.function-call.[identifier|arguments].

This solution makes general purpose selectors to match all import lists hard. You always need to address meta.statment.import.import-list, meta.statement.whatever.import-list, ...

Hence it should be used with care and only if the statements structure is very clear.

About snippet 2 (C++)

That’s an older versions of function calls. The is a very general scope used to denote all kinds of parenthesed code sections. A selector targeting meta.function-call - meta.function-call meta.block could be used to match function call arguments.

More recent syntaxes use meta.function-call.arguments only or in conjunction with
This makes selectors a bit more straight forward: meta.function-call.arguments is enough to target function call arguments, while if present could be used for general purpose functions if someone wants to match all kinds of parenthesed sections no matter if they are function arguments, or expression groups.

The punctuation is the highlighting part, which may be addressed by color schemes to give the parentheses their color. It is part of the scope naming guidelines that the related meta scope of a section should span the opening and closing punctuation. Hence those are stacked.

About snippet 3 Pthon lambda

Here two highlighting scopes storage.type and keyword.declaration are stacked because of some kind of backward compatibility considerations with older color schemes.

While the return value of a function in C/C++ like int or char may clearly be scoped storage.type situation is more confuse in dynamically typed syntaxes like python, which just use general keywords like def to denote function definitions. Those keywords were scoped storage.type.function in the past.

Same applies to class keyword. But … in a statement like class ClassName, what is the type? Is it class or ClassName? We came up with class to be a keyword.declaration which declares a data type. Same applies to python functions/lambdas. Those keywords are more like declaration keywords than data types. Hence those were changed.

It turned out stacking those is not as backward compatible as desired. The internal ST4 beta comes with a core feature to maintain backward compatibility with older color schemes even if we remove the legacy storage.type in such constructs. One thing still on the TODO.

Hope, that helps a bit.



Great, it makes it clear. The only thing left is that in the first example the meta.statement.import.python scope is already pushed onto the stack in line 168. What is the reason for repeating it in line 176?

1 Like


The reason are the set statements, which replace the context of line 168 by the one around 176 at the current stack slot/level. Hence any information including meta scopes is no longer available.

set is used here in order to make pop: true in line 179 pop off the whole imports-from-import-body off stack and continue with whatever top-level context comes next.