Skip to content

Fix single-character code blocks rendering as empty#18572

Merged
krassowski merged 3 commits intojupyterlab:mainfrom
soniya-malviy:language.ts
Feb 27, 2026
Merged

Fix single-character code blocks rendering as empty#18572
krassowski merged 3 commits intojupyterlab:mainfrom
soniya-malviy:language.ts

Conversation

@soniya-malviy
Copy link
Copy Markdown
Contributor

@soniya-malviy soniya-malviy commented Feb 27, 2026

References

This PR fixes a bug in JupyterLab’s highlighting helper (/packages/codemirror/src/language.ts) where single-character code blocks like x rendered as empty blocks.

Root cause:

The helper used tree.length - 1 (number of syntax tree nodes) instead of code.length (actual string length) and did not handle the case where highlightTree emits no tokens. As a result, unrecognized single-character identifiers could be dropped even though the input code string was valid.

This PR:

  • Adds a fallback that renders the raw code string when no tokens are emitted.
  • Uses code.length to compute the remaining text instead of tree.length.
  • This addresses the issue at the source of the data loss in JupyterLab’s wrapper code, rather than post-processing rendered HTML. It applies to all code highlighting that uses this helper, not just markdown.
  • I previously attempted to fix this in renderers.ts, but that approach only treated the symptom (empty <code> blocks) and required HTML post-processing. This PR replaces that with a root-cause fix in language.ts.

Code changes

BEFORE (Buggy Code):

File: packages/codemirror/src/language.ts (lines 250-253)


 if (pos < tree.length - 1) {
      // No style applied on the trailing text
      el.appendChild(document.createTextNode(code.slice(pos, tree.length)));
    }

AFTER (Fixed Code):


 if (pos === 0 && code.length > 0) {
      // No tokens were emitted (e.g., single unrecognized character like 'x')
      // Render the entire code as plain text fallback
      el.appendChild(document.createTextNode(code));
    } else if (pos < code.length) {
      // Handle any remaining unstyled content
      el.appendChild(document.createTextNode(code.slice(pos, code.length)));
    }

Proofs

jupyterlab.mp4

@jupyterlab-probot
Copy link
Copy Markdown

Thanks for making a pull request to jupyterlab!
To try out this branch on binder, follow this link: Binder

Copy link
Copy Markdown
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you for investigating it! Can you also add a test case that would fail before and pass after this change in:

describe('#highlight', () => {
it('should load a defined spec', async () => {
const container = document.createElement('pre');
await languages.highlight(
`(defun check-login (name password) ; absolutely secure
(if (equal name "admin")
(equal password "12345")
#t))`,
languages.findBest('text/foo'),
container
);
expect(container.innerHTML).toEqual(
`<span class="ͼ19">(</span>defun check-login <span class="ͼ19">(</span>name password<span class="ͼ19">)</span> <span class="ͼ11">; absolutely secure</span>
<span class="ͼ19">(</span>if <span class="ͼ19">(</span>equal name <span class="ͼ12">"admin"</span><span class="ͼ19">)</span>
<span class="ͼ19">(</span>equal password <span class="ͼ12">"12345"</span><span class="ͼ19">)</span>
<span class="ͼ1d">#t</span><span class="ͼ19">)</span><span class="ͼ19">)</span>`
);
});
});

@krassowski krassowski added this to the 4.5.x milestone Feb 27, 2026
@soniya-malviy
Copy link
Copy Markdown
Contributor Author

Great, thank you for investigating it! Can you also add a test case that would fail before and pass after this change in:

describe('#highlight', () => {
it('should load a defined spec', async () => {
const container = document.createElement('pre');
await languages.highlight(
`(defun check-login (name password) ; absolutely secure
(if (equal name "admin")
(equal password "12345")
#t))`,
languages.findBest('text/foo'),
container
);
expect(container.innerHTML).toEqual(
`<span class="ͼ19">(</span>defun check-login <span class="ͼ19">(</span>name password<span class="ͼ19">)</span> <span class="ͼ11">; absolutely secure</span>
<span class="ͼ19">(</span>if <span class="ͼ19">(</span>equal name <span class="ͼ12">"admin"</span><span class="ͼ19">)</span>
<span class="ͼ19">(</span>equal password <span class="ͼ12">"12345"</span><span class="ͼ19">)</span>
<span class="ͼ1d">#t</span><span class="ͼ19">)</span><span class="ͼ19">)</span>`
);
});
});

@krassowski sure

Copy link
Copy Markdown
Member

@krassowski krassowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @soniya-malviy!

@krassowski krassowski merged commit 4e38172 into jupyterlab:main Feb 27, 2026
104 checks passed
meeseeksmachine pushed a commit to meeseeksmachine/jupyterlab that referenced this pull request Feb 27, 2026
@soniya-malviy
Copy link
Copy Markdown
Contributor Author

Thank you @soniya-malviy!

Thank you @krassowski for reviewing and merging it—thrilled to be part of the project! 🚀

krassowski pushed a commit that referenced this pull request Feb 27, 2026
…rendering as empty) (#18574)

Backport PR #18572: Fix single-character code blocks rendering as empty

Co-authored-by: Soniya Malviya <151515611+soniya-malviy@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Code with single variable renders into empty block

2 participants