diff --git a/src/markdown-code-class.ts b/src/markdown-code-class.ts new file mode 100644 index 00000000..921cba70 --- /dev/null +++ b/src/markdown-code-class.ts @@ -0,0 +1,27 @@ +// Copyright (c) Mehmet Bektas + +// 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 `` +// and the outer `
`.
+export function resolveCodeClassName(
+  children: unknown,
+  className?: string
+): string | undefined {
+  const isTrulyInline = !String(children).includes('\n');
+  return isTrulyInline ? `inline-code ${className || ''}`.trim() : className;
+}
diff --git a/src/markdown-renderer.tsx b/src/markdown-renderer.tsx
index 6c0b4fbd..d6fea56c 100644
--- a/src/markdown-renderer.tsx
+++ b/src/markdown-renderer.tsx
@@ -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;
@@ -104,7 +105,10 @@ export function MarkdownRenderer({
 
           if (inline || !match) {
             return (
-              
+              
                 {children}
               
             );
diff --git a/style/base.css b/style/base.css
index 8f6c560a..a9613e93 100644
--- a/style/base.css
+++ b/style/base.css
@@ -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;
@@ -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);
 }
 
diff --git a/tests/ts/markdown-code-class.test.ts b/tests/ts/markdown-code-class.test.ts
new file mode 100644
index 00000000..3d032ed1
--- /dev/null
+++ b/tests/ts/markdown-code-class.test.ts
@@ -0,0 +1,29 @@
+// Copyright (c) Mehmet Bektas 
+
+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');
+  });
+});