diff --git a/frontend/src/components/CodeBlock.tsx b/frontend/src/components/CodeBlock.tsx index e9ad6549..45b5932a 100644 --- a/frontend/src/components/CodeBlock.tsx +++ b/frontend/src/components/CodeBlock.tsx @@ -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'; @@ -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); @@ -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); diff --git a/frontend/src/components/ToolPill.tsx b/frontend/src/components/ToolPill.tsx index 4cb6e5f2..8d59038c 100644 --- a/frontend/src/components/ToolPill.tsx +++ b/frontend/src/components/ToolPill.tsx @@ -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, @@ -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 ( -
- onPopOut(raw.path!) : undefined} - /> -
- ); - } + const isRead = raw?.type === 'read'; return (
- Result -
{block.toolResult}
+ onPopOut(raw.path!) : undefined} + />
); } diff --git a/frontend/src/components/__tests__/ToolPill.test.tsx b/frontend/src/components/__tests__/ToolPill.test.tsx index 19bb74f3..eb31a287 100644 --- a/frontend/src/components/__tests__/ToolPill.test.tsx +++ b/frontend/src/components/__tests__/ToolPill.test.tsx @@ -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', @@ -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()); + 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()); + 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', () => { diff --git a/packages/protocol/__tests__/language.test.ts b/packages/protocol/__tests__/language.test.ts index d5e89d4b..20a1438e 100644 --- a/packages/protocol/__tests__/language.test.ts +++ b/packages/protocol/__tests__/language.test.ts @@ -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'); + }); }); diff --git a/packages/protocol/src/language.ts b/packages/protocol/src/language.ts index 46706fb1..22a72ba7 100644 --- a/packages/protocol/src/language.ts +++ b/packages/protocol/src/language.ts @@ -56,6 +56,25 @@ const EXT_MAP: Record = { kts: 'kotlin', swift: 'swift', cs: 'csharp', + m: 'objectivec', + 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', @@ -64,13 +83,21 @@ const EXT_MAP: Record = { 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', + nix: 'nix', + wat: 'wasm', + wast: 'wasm', // SQL sql: 'sql',