Skip to content

tui: PHP code blocks without <?php are not syntax highlighted #58664

Closed

Description

@ganyicz

Description

The TUI shows PHP code blocks in assistant messages as plain text unless the snippet starts with <?php. Models usually leave the tag out of short excerpts, so most PHP in a session ends up unhighlighted. Other languages don't have this problem.

Plugins

Only local plugins, none of which touch Markdown or syntax highlighting

OpenCode version

2.1.4 (also seen on 2.1.3)

Steps to reproduce

  1. Have the assistant reply with this Markdown:

    ```php
    $compiled = parent::getCompiledPath($path);
    
    if ($this->echoFormat === self::DEFAULT_ECHO_FORMAT) {
        return $compiled;
    }
    ```
  2. The block shows as plain text.

  3. Add <?php as the first line of the block, and it highlights.

Operating System

macOS 27.0 (arm64)

Terminal

Ghostty

Activity

  1. opencode-agent commented on Oct 24, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report. I could reproduce this on 2.1.4.

    In a simulated session, the assistant replied with your snippet twice: once as a plain ```php block, and once with <?php added as the first line. The block without the tag renders as plain text, and the one with it is fully highlighted. It still happens on the latest dev (f20760b). The recording and screenshots below show it, and the script below reproduces it.

    The likely cause is that the TUI loads tree-sitter's HTML-embedded php grammar, which only starts parsing PHP after <?php. The same tree-sitter-php release also has a php_only grammar for bare PHP. A maintainer still needs to confirm this.

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Reproduces anomalyco/opencode#58664: ```php blocks without `<?php` are not highlighted in the TUI.
    import { Effect, Stream } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const reply = [
      "Without the tag:",
      "",
      "```php",
      "$compiled = parent::getCompiledPath($path);",
      "",
      "if ($this->echoFormat === self::DEFAULT_ECHO_FORMAT) {",
      "    return $compiled;",
      "}",
      "```",
      "",
      "With the tag:",
      "",
      "```php",
      "<?php",
      "$compiled = parent::getCompiledPath($path);",
      "",
      "if ($this->echoFormat === self::DEFAULT_ECHO_FORMAT) {",
      "    return $compiled;",
      "}",
      "```",
      "",
      "Done.",
    ].join("\n")
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true, viewport: { cols: 120, rows: 45 } },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          yield* ui.screenshot("home")
          // Answer every model request (including any title request) with the reply.
          yield* llm.serve(() => Stream.make(Llm.text(reply)))
          yield* ui.submit("Show me a PHP snippet")
          yield* ui.waitFor("Done.")
          yield* Effect.sleep(3000)
          yield* ui.screenshot("php-blocks")
          yield* Effect.sleep(1000)
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. Dopetaiga commented on Oct 24, 2026

    @Dopetaiga

    I'd like to take this. Root cause: the PHP grammar parses anything outside <?php ... ?> as plain text nodes, so a block without the opening tag produces no highlightable nodes at all. The upstream project ships a dedicated tree-sitter-php_only.wasm variant that treats the whole document as PHP — and the bundled highlights query references no text/program nodes, so it works unchanged against the variant. Plan: point the PHP parser entry at the php_only wasm (blocks that do include <?php still highlight through error-tolerant parsing, losing only the exact tag styling). Will verify by driving the highlight API directly.

  3. fihaaade commented on Oct 27, 2026

    @fihaaade
    Contributor

    @SwastikTripathi #58675 was auto-closed for the PR template, not the change itself. Are you planning to reopen it? If not, I'm happy to pick it up as the one-line tree-sitter-php_only.wasm swap that keeps the existing tree-sitter-php query (per @Dopetaiga's measurements), with credit to you both.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions