lezer-parser/highlight

No longer applying inherited classes to mounted trees breaks use-case

Closed this issue · 4 comments

acnebs commented

Originally reported here but moved to this repo because figured this was a better place for it when I realized what the issue was.

Anyway, this commit seems to break our use-case, which is to apply certain consistent styles to all codeblocks when using CodeMirror for markdown. This is the basic repro:

const highlighting = syntaxHighlighting(
  HighlightStyle.define([
    {
      tag: customTags.code,
      lineHeight: EDITOR_LINE_HEIGHT,
      fontFamily: 'FiraCode',
    }
  ])
);

const styleCode: MarkdownExtension = {
  props: [
    styleTags({
      'CodeBlock/...': customTags.code,
      'FencedCode/...': customTags.code,
      'InlineCode CodeText': customTags.code,
    }),
  ],
};

const state = EditorState.create({
  doc: "Hello\n\n```javascript\nlet x = 'y';\n```\n\n```\nconst z = 3;\n```",
  extensions: [
    basicSetup,
    highlighter,
    markdown({
      extensions: [codeStyler],
      codeLanguages: languages, 
    }),
  ], 
});

For @lezer/highlight > 1.1.4, the anonymous class that is generated for the customTags.code highlight is no longer applied to code blocks if a language has been specified (since that is a "mounted tree").

Is it still possible to get the desired behavior after 1.1.5? Happy to change how we're doing this but for now have pinned 1.1.4 so that all code blocks remain styled consistently.

Indeed, this pattern no longer works after that fix. You'll have to invert the logic—make the code font the default and add a highlight style with scope and all values to add another font to the Markdown content. Does that work for you?

acnebs commented

Thanks for the tip. I'm trying this now, but struggling a bit as it is now much less clear which order the CSS classes (and therefore styles) should be applied in and/or I don't really seem to have control over this anymore.

For example, I want (non-markdown) code blocks to be fontFamily: monospace, markdown "plaintext" to be fontFamily: Helvetica, and then most markdown "marks" to be fontFamily: monospace as well.

const codeRootStyle = {
  fontFamily: 'monospace',
};

const markdownRootStyle = {
  fontFamily: 'Helvetica',
};

const codeHighlighter = syntaxHighlighting(
  HighlightStyle.define([], { all: codeRootStyle })
);

const markdownHighlighter = syntaxHighlighting(
  HighlightStyle.define([], { all: markdownRootStyle, scope: markdownLanguage })
);

const unifiedHighlighter = syntaxHighlighting(
  HighlightStyle.define([
    // markdown features styled like code
    {
      tag: [customTags.code, customTags.latex, tags.monospace, customTags.table, tags.url],
      ...codeRootStyle,
    },
  ])
);

export const highlighters = [
  unifiedHighlighter,
  markdownHighlighter,
  codeHighlighter,
];

I tried something like the above code, but it is giving precedence to markdownHighlighter for the markdown features that should be highlighted with unifiedHighlighter, e.g. the font for tags.url ends up being Helvetica instead of monospace, due to the order classes are being added to the lines. This is true regardless of whether I put unifiedHighlighter before or after markdownHighlighter in the highlighters array (which I am then just spreading in the extensions array in state init)

So @acnebs and I went ahead and finished modifying the code, inverting the logic as you suggested @marijnh; thanks very much for the prompt reply.

We noticed that it wasn't possible to apply the same codeRootStyle to multiple different tags at the same time. @Acneb's previous example above was premature so we just applied each style individually:

// initially tried this
const unifiedHighlighter = syntaxHighlighting(
  HighlightStyle.define([
    // markdown features styled like code
    {
      tag: [customTags.code, customTags.latex, tags.monospace, customTags.table, tags.url],
      ...codeRootStyle,
    },
  ])
);

// this worked
const unifiedHighlighter = syntaxHighlighting(
  HighlightStyle.define([
    // markdown features styled like code
    { tag: customTags.code, ...codeRootStyle },
    { tag: customTags.latex, ...codeRootStyle },
    { tag: customTags.table, ...codeRootStyle },
    { tag: tags.monospace, ...codeRootStyle },
    { tag: tags.url, ...codeRootStyle },
  ])
);

We've pushed this to production although are now receiving user reports that editors with a large amount of content flash with the incorrect styles initially showing all text as fontFamily: monospaced and then applying the styles to show fontFamily: Helvetica.

This is most probably also because we have a lot of extensions and logic running when the codemirror is first rendered. A bit of a shot in the dark but I'm guessing there's not an easy way around this now? Would it be possible to add back in the anonymous class generated for customTags.code?

We noticed that it wasn't possible to apply the same codeRootStyle to multiple different tags at the same time.

The code you show should work (and when I try similar code it works for me).

receiving user reports that editors with a large amount of content flash with the incorrect styles initially showing

Right, before there is a parse tree, the document won't be highlighted, and you'll see the default unstyled font. Parsing for big inputs is done asynchronously (so as to not freeze the interface).