Determining innermost syntax context at cursor position in GtkSourceView

I’m building a typst editor in GTK4/Rust using GtkSourceView5, and I’m trying to fix a quirk with delimiter auto-pairing (e.g. auto-closing *...*, _..._, $...$, (...)). The correct behaviour depends on which typst syntax context the cursor is currently in, specifically whether it’s in markup mode, code mode, or math mode, since different delimiters are valid in different modes.

Typst contexts can be arbitrarily nested. For example:

#(          ← code (parentheses block)
  [         ← markup (content block)
    #(      ← code again
      [     ← markup again
        $   ← math

I have a custom .lang file with contexts classed code, markup, and math. My problem is determining which of these is the innermost active context at the cursor.

I’ve tried get_context_classes_at_iter returns all active classes as a flat list, e.g. ["no-spell-check", "code", "markup"]. The ordering isn’t reliable enough to determine innermost context, as it varies depending on nesting depth and which contexts are structurally outer containers vs. actual content contexts. For instance, if we have a code mode context encompassing a markup mode context encompassing another code mode context (this sounds convoluted, but is actually rather common when designing templates and the like), the code context is specified once, before the markup context. Selecting only the final context from get_context_classes_at_iter therefore incorrectly gives us markup.

Is there a correct way to determine the innermost/most-specific active syntax context at a given position using the GtkSourceView API? Or is there a better approach for implementing context-sensitive editor behaviour like delimiter pairing? Any advice on language file design that would make this easier would also be very welcome.

Maybe doable with gtk_source_buffer_iter_backward_to_context_class_toggle() and forward.

But what you probably want is a code model for the document/file. I.e. an AST (Abstract Syntax Tree [1]). That way you can achieve much more easily features such as context-sensitive completion, refactoring features, have a table of contents in the side panel, things like that.

[1] If you never heard that CS term, you probably want to read the beginning of a book on compilers. Scanning, parsing and context-sensitive analysis (the frontend of a compiler, basically).

Note: I provide professional services around text editors and IDEs based on gedit, libgedit and GtkSourceView. If you’re running a business and want to collaborate, see: gedit B2B Services