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
24 changes: 24 additions & 0 deletions frontend/src/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ import { CopyButton } from './CopyButton';
// IMPORTANT: Keep in sync with EXT_MAP in packages/protocol/src/language.ts
import bash from 'highlight.js/lib/languages/bash';
import c from 'highlight.js/lib/languages/c';
import clojure from 'highlight.js/lib/languages/clojure';
import cmake from 'highlight.js/lib/languages/cmake';
import cpp from 'highlight.js/lib/languages/cpp';
import csharp from 'highlight.js/lib/languages/csharp';
import css from 'highlight.js/lib/languages/css';
import dart from 'highlight.js/lib/languages/dart';
import diff from 'highlight.js/lib/languages/diff';
import dockerfile from 'highlight.js/lib/languages/dockerfile';
import elixir from 'highlight.js/lib/languages/elixir';
import erlang from 'highlight.js/lib/languages/erlang';
import go from 'highlight.js/lib/languages/go';
import graphql from 'highlight.js/lib/languages/graphql';
import haskell from 'highlight.js/lib/languages/haskell';
import ini from 'highlight.js/lib/languages/ini';
import java from 'highlight.js/lib/languages/java';
import javascript from 'highlight.js/lib/languages/javascript';
Expand All @@ -25,30 +30,42 @@ import less from 'highlight.js/lib/languages/less';
import lua from 'highlight.js/lib/languages/lua';
import makefile from 'highlight.js/lib/languages/makefile';
import markdown from 'highlight.js/lib/languages/markdown';
import nginx from 'highlight.js/lib/languages/nginx';
import nix from 'highlight.js/lib/languages/nix';
import objectivec from 'highlight.js/lib/languages/objectivec';
import ocaml from 'highlight.js/lib/languages/ocaml';
import perl from 'highlight.js/lib/languages/perl';
import php from 'highlight.js/lib/languages/php';
import powershell from 'highlight.js/lib/languages/powershell';
import protobuf from 'highlight.js/lib/languages/protobuf';
import python from 'highlight.js/lib/languages/python';
import r from 'highlight.js/lib/languages/r';
import ruby from 'highlight.js/lib/languages/ruby';
import rust from 'highlight.js/lib/languages/rust';
import scala from 'highlight.js/lib/languages/scala';
import scss from 'highlight.js/lib/languages/scss';
import sql from 'highlight.js/lib/languages/sql';
import swift from 'highlight.js/lib/languages/swift';
import typescript from 'highlight.js/lib/languages/typescript';
import wasm from 'highlight.js/lib/languages/wasm';
import xml from 'highlight.js/lib/languages/xml';
import yaml from 'highlight.js/lib/languages/yaml';

hljs.registerLanguage('bash', bash);
hljs.registerLanguage('c', c);
hljs.registerLanguage('clojure', clojure);
hljs.registerLanguage('cmake', cmake);
hljs.registerLanguage('cpp', cpp);
hljs.registerLanguage('csharp', csharp);
hljs.registerLanguage('css', css);
hljs.registerLanguage('dart', dart);
hljs.registerLanguage('diff', diff);
hljs.registerLanguage('dockerfile', dockerfile);
hljs.registerLanguage('elixir', elixir);
hljs.registerLanguage('erlang', erlang);
hljs.registerLanguage('go', go);
hljs.registerLanguage('graphql', graphql);
hljs.registerLanguage('haskell', haskell);
hljs.registerLanguage('ini', ini);
hljs.registerLanguage('java', java);
hljs.registerLanguage('javascript', javascript);
Expand All @@ -59,17 +76,24 @@ hljs.registerLanguage('less', less);
hljs.registerLanguage('lua', lua);
hljs.registerLanguage('makefile', makefile);
hljs.registerLanguage('markdown', markdown);
hljs.registerLanguage('nginx', nginx);
hljs.registerLanguage('nix', nix);
hljs.registerLanguage('objectivec', objectivec);
hljs.registerLanguage('ocaml', ocaml);
hljs.registerLanguage('perl', perl);
hljs.registerLanguage('php', php);
hljs.registerLanguage('powershell', powershell);
hljs.registerLanguage('protobuf', protobuf);
hljs.registerLanguage('python', python);
hljs.registerLanguage('r', r);
hljs.registerLanguage('ruby', ruby);
hljs.registerLanguage('rust', rust);
hljs.registerLanguage('scala', scala);
hljs.registerLanguage('scss', scss);
hljs.registerLanguage('sql', sql);
hljs.registerLanguage('swift', swift);
hljs.registerLanguage('typescript', typescript);
hljs.registerLanguage('wasm', wasm);
hljs.registerLanguage('xml', xml);
hljs.registerLanguage('yaml', yaml);

Expand Down
26 changes: 9 additions & 17 deletions frontend/src/components/ToolPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function RawInputDetail({
return null;
}

/** Render tool result syntax-highlighted for read/write tools, plain for others. */
/** Render tool result with syntax highlighting when language is known. */
function ToolResult({
block,
onPopOut,
Expand All @@ -87,25 +87,17 @@ function ToolResult({
if (block.toolResult === undefined) return null;

const raw = block.rawInput;
// For Read tool results, show the file content with syntax highlighting
if (raw?.type === 'read' && block.toolResult) {
return (
<div className="tool-pill-section">
<CodeBlock
code={block.toolResult}
language={raw.language}
label={raw.path}
maxHeight={400}
onPopOut={raw.path && onPopOut ? () => onPopOut(raw.path!) : undefined}
/>
</div>
);
}
const isRead = raw?.type === 'read';

return (
<div className="tool-pill-section">
<span className="tool-pill-label">Result</span>
<pre className="tool-pill-pre">{block.toolResult}</pre>
<CodeBlock
code={block.toolResult}
language={raw?.language}
label={isRead ? raw?.path : 'Result'}
maxHeight={400}
onPopOut={isRead && raw?.path && onPopOut ? () => onPopOut(raw.path!) : undefined}
/>
</div>
);
}
Expand Down
47 changes: 44 additions & 3 deletions frontend/src/components/__tests__/ToolPill.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('ToolPill', () => {
expect(container.querySelector('.code-block-highlight')).toBeTruthy();
});

it('renders plain pre for non-read tools', () => {
it('renders CodeBlock for non-read tools', () => {
const block: FinishedBlock = {
blockId: 'b1',
blockType: 'tool_use',
Expand All @@ -187,8 +187,49 @@ describe('ToolPill', () => {
fireEvent.click(screen.getByRole('button'));

expect(screen.getByText('Result')).toBeTruthy();
expect(container.querySelector('.tool-pill-pre')).toBeTruthy();
expect(container.querySelector('.code-block-highlight')).toBeNull();
expect(container.querySelector('.code-block-highlight')).toBeTruthy();
});

it('renders bash-highlighted result for command tools', () => {
const block: FinishedBlock = {
blockId: 'b1',
blockType: 'tool_use',
content: '',
toolName: 'Bash',
toolInput: 'echo hello',
toolResult: 'hello',
rawInput: { type: 'command', command: 'echo hello', language: 'bash' },
};
const { container } = render(wrap(<ToolPill block={block} />));
fireEvent.click(screen.getByRole('button'));

// Result section uses CodeBlock with bash language
const codeBlocks = container.querySelectorAll('.code-block-highlight');
expect(codeBlocks.length).toBeGreaterThanOrEqual(2); // command input + result
expect(screen.getByText('Result')).toBeTruthy();
});

it('renders file-language-highlighted result for write tools', () => {
const block: FinishedBlock = {
blockId: 'b1',
blockType: 'tool_use',
content: '',
toolName: 'Write',
toolInput: 'test.ts',
toolResult: 'Wrote 50 chars',
rawInput: {
type: 'write',
path: '/src/test.ts',
contents: 'const x = 1;',
language: 'typescript',
},
};
const { container } = render(wrap(<ToolPill block={block} />));
fireEvent.click(screen.getByRole('button'));

// Both input and result render via CodeBlock
const codeBlocks = container.querySelectorAll('.code-block-highlight');
expect(codeBlocks.length).toBeGreaterThanOrEqual(2);
});

it('returns null when no toolResult', () => {
Expand Down
53 changes: 53 additions & 0 deletions packages/protocol/__tests__/language.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,57 @@ describe('languageFromPath', () => {
it('handles dotfiles with known extension', () => {
expect(languageFromPath('.bashrc')).toBeUndefined(); // no ext after dot
});

// New language coverage
it('detects Objective-C from .m', () => {
expect(languageFromPath('AppDelegate.m')).toBe('objectivec');
});

it('detects Dart from .dart', () => {
expect(languageFromPath('lib/main.dart')).toBe('dart');
});

it('detects Scala from .scala', () => {
expect(languageFromPath('src/Main.scala')).toBe('scala');
});

it('detects Haskell from .hs', () => {
expect(languageFromPath('src/Main.hs')).toBe('haskell');
});

it('detects OCaml from .ml', () => {
expect(languageFromPath('lib/parser.ml')).toBe('ocaml');
});

it('detects Clojure from .clj', () => {
expect(languageFromPath('src/core.clj')).toBe('clojure');
});

it('detects ClojureScript from .cljs', () => {
expect(languageFromPath('src/app.cljs')).toBe('clojure');
});

it('detects Elixir from .ex', () => {
expect(languageFromPath('lib/app.ex')).toBe('elixir');
});

it('detects Erlang from .erl', () => {
expect(languageFromPath('src/server.erl')).toBe('erlang');
});

it('detects PowerShell from .ps1', () => {
expect(languageFromPath('scripts/deploy.ps1')).toBe('powershell');
});

it('detects Nix from .nix', () => {
expect(languageFromPath('flake.nix')).toBe('nix');
});

it('detects WASM text from .wat', () => {
expect(languageFromPath('module.wat')).toBe('wasm');
});

it('detects nginx from .conf', () => {
expect(languageFromPath('/etc/nginx/nginx.conf')).toBe('nginx');
});
});
27 changes: 27 additions & 0 deletions packages/protocol/src/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ const EXT_MAP: Record<string, string> = {
kts: 'kotlin',
swift: 'swift',
cs: 'csharp',
m: 'objectivec',
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: Mapping .mobjectivec conflicts with MATLAB/Octave, which also uses .m files. In codebases that use MATLAB, this will produce incorrect highlighting. No great solution — just worth documenting the ambiguity as a code comment.

mm: 'objectivec',
dart: 'dart',
scala: 'scala',
sbt: 'scala',

// Functional
hs: 'haskell',
lhs: 'haskell',
ml: 'ocaml',
mli: 'ocaml',
clj: 'clojure',
cljs: 'clojure',
cljc: 'clojure',
edn: 'clojure',
ex: 'elixir',
exs: 'elixir',
erl: 'erlang',
hrl: 'erlang',

// Scripting
rb: 'ruby',
Expand All @@ -64,13 +83,21 @@ const EXT_MAP: Record<string, string> = {
php: 'php',
r: 'r',
R: 'r',
ps1: 'powershell',
psm1: 'powershell',
psd1: 'powershell',

// Markup / Docs
md: 'markdown',
mdx: 'markdown',
tex: 'latex',

// Infrastructure
dockerfile: 'dockerfile',
conf: 'nginx',
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: Mapping .confnginx is too broad. Many non-nginx files use .conf (e.g. httpd.conf for Apache, sysctl.conf, resolv.conf, logrotate.conf, application configs). Applying nginx syntax highlighting to these will produce misleading results. Consider removing this mapping or using basename-level detection (e.g. only match files containing 'nginx' in the name). [fixable]

nix: 'nix',
wat: 'wasm',
wast: 'wasm',

// SQL
sql: 'sql',
Expand Down
Loading