Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/markdown-code-class.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>

// Kept dependency-free (no React/react-markdown imports) so it can be unit
// tested directly under Jest: markdown-renderer.tsx imports react-markdown
// at module scope, which ships ESM `export` syntax that jest.config.js's
// default (no `transformIgnorePatterns` override) can't parse — anything
// importing markdown-renderer.tsx transitively fails at parse time before a
// test can run (see the reverted MarkdownRenderer tests from PR #385).

// react-markdown@9 never sets `inline` (removed upstream), so the `code`
// component's `if (inline || !match)` branch also catches a fenced block
// with an unrecognized/missing language — that case keeps its default
// `pre > code` nesting from remark-rehype, unlike the SyntaxHighlighter
// branch, so it shouldn't get inline-code styling either. Distinguish the
// two by content shape instead: remark-rehype always appends a trailing
// `\n` to fenced code (even single-line), while true inline code never
// contains one. Only genuine inline code gets `.inline-code`, so CSS can
// target it directly instead of via `:not(pre) > code`, which broke once
// `PreTag="div"` put a wrapper div between a highlighted block's `<code>`
// and the outer `<pre>`.
export function resolveCodeClassName(
children: unknown,
className?: string
): string | undefined {
const isTrulyInline = !String(children).includes('\n');
return isTrulyInline ? `inline-code ${className || ''}`.trim() : className;
}
6 changes: 5 additions & 1 deletion src/markdown-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PathExt } from '@jupyterlab/coreutils';
import { MarkdownLink } from './components/markdown-link';
import { isDarkTheme, writeTextToClipboard } from './utils';
import { IActiveDocumentInfo } from './tokens';
import { resolveCodeClassName } from './markdown-code-class';

type MarkdownRendererProps = {
children: string;
Expand Down Expand Up @@ -104,7 +105,10 @@ export function MarkdownRenderer({

if (inline || !match) {
return (
<code className={className} {...props}>
<code
className={resolveCodeClassName(children, className)}
{...props}
>
{children}
</code>
);
Expand Down
4 changes: 2 additions & 2 deletions style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ pre:has(.code-block-header) {
font-size: 13px;
}

.chat-message-content :not(pre) > code {
.chat-message-content code.inline-code {
background-color: var(--jp-layout-color2);
padding: 1px 4px;
border-radius: 3px;
Expand Down Expand Up @@ -1794,7 +1794,7 @@ button.send-button:disabled:active:not(.send-button-stop) {

/* Inline code also fills with --jp-layout-color2, which now matches this box's
fill; recess nested code to --jp-layout-color1 so it stays distinct. */
.expandable-content-text :not(pre) > code {
.expandable-content-text code.inline-code {
background-color: var(--jp-layout-color1);
}

Expand Down
29 changes: 29 additions & 0 deletions tests/ts/markdown-code-class.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Mehmet Bektas <mbektasgh@outlook.com>

import { resolveCodeClassName } from '../../src/markdown-code-class';

describe('resolveCodeClassName', () => {
it('marks single-line content (true inline code) with inline-code', () => {
expect(resolveCodeClassName('inline_var')).toBe('inline-code');
});

it('preserves an existing className alongside inline-code', () => {
expect(resolveCodeClassName('inline_var', 'some-class')).toBe(
'inline-code some-class'
);
});

it('does not mark multi-line content (a highlighted fenced block)', () => {
expect(
resolveCodeClassName('import json\nimport logging\n', 'language-python')
).toBe('language-python');
});

it('does not mark multi-line content with no language class (unmatched fence)', () => {
expect(resolveCodeClassName('plain text block\n')).toBeUndefined();
});

it('does not mark a single-line fenced block that still carries a trailing newline', () => {
expect(resolveCodeClassName('x\n', 'language-text')).toBe('language-text');
});
});
Loading