Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Last PLNew to Visual Studio Code? Get it now.
Last PL

Last PL

Moon Presence

|
2 installs
| (0) | Free
Syntax highlighting, IntelliSense, and diagnostics for HTML, CSS, and JavaScript embedded inside Oracle web PL/SQL procedures (HTP.P, OWA_UTIL, q-quoted literals, tagged comments).
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Last PL — VS Code Extension

Last PL (last-pl) — Web PL/SQL embedded languages.

Native-feeling editing for HTML, CSS, and JavaScript embedded inside Oracle web PL/SQL — HTP.P, HTF.*, OWA_UTIL.*, APEX_JAVASCRIPT.*, APEX_CSS.*, and /*html*/ / /*css*/ / /*js*/ tagged string literals. Highlighting, IntelliSense, diagnostics (HTMLHint + vscode-css-languageservice + ESLint), and formatting (structure-aware PL/SQL re-indentation + Prettier/html/css language services for the embedded code) — all delivered through one extension.

Quick start

  1. Install Web PL/SQL Embedded Languages from the marketplace, plus a PL/SQL grammar provider (this extension rides on whichever you use):
    • Oracle SQL Developer Extension for VSCode — language oracle-sql, or
    • xyz.plsql-language — language plsql. If you run both, pick one to own your PL/SQL files (they both claim .pks / .sql / …): disable the other, or set files.associations. This extension layers onto whichever owns the file.
  2. Open any .pks / .pkb / .sql / .pls etc. file containing HTP.P('<div>…</div>') or similar — the embedded HTML/CSS/JS is highlighted, validated, and formattable immediately.
  3. Status bar (bottom-right) shows $(globe) PL/SQL: N regions when an embedded-content file is active. Click it to list every detected region.
  4. Cmd-Shift-P → "PL/SQL: Diagnose Setup" is the troubleshooting entry point — confirms the host grammar is installed, lists regions, dumps resolved diagnostics config.

What works today

  • Grammar injection into source.plsql, source.plsql.oracle, and source.oracle-sql. No host PL/SQL extension is replaced — this one rides alongside yours. Works with xyz.plsql-language (language plsql) and the Oracle SQL Developer Extension (language oracle-sql); diagnostics are grammar-agnostic and run on either.

  • Automatic HTML highlighting inside any call of the shape HTP.<anything>(...), HTF.<anything>(...), or OWA_UTIL.<anything>(...). JS is extracted from APEX_JAVASCRIPT.* calls and CSS from APEX_CSS.* calls. String arguments of any form — regular '...' or q-quoted q'[...]', q'{...}', q'(...)', q'<...>', q'!...!', q'|...|' — are tokenized as HTML and get nested CSS (<style>) and JS (<script>) highlighting for free via the built-in HTML grammar.

  • Tagged-comment escape hatch for strings outside call context:

    v_fragment := /*html*/ '<div class="card">...</div>';
    v_style    := /*css*/  q'{ .card { padding: 1rem; } }';
    v_script   := /*js*/   q'< const x = 1; console.log(x); >';
    

    The tag must immediately precede the string. javascript is accepted as an alias for js.

  • Native-feeling editing inside embedded regions (v0.2 → v0.15). Requests are forwarded to VS Code's built-in HTML/CSS/JS language services via virtual documents. The forwarded provider list as of v0.15 includes:

    • Completion, hover, definition, type-definition, declaration, implementation, references, document highlights, signature help.
    • Document symbols (outline view), folding ranges, document links, selection range (smart-expand), linked editing range (auto-rename matching tag).
    • Color picker / gutter swatches via document-color + color-presentation providers.
    • Auto-close matching HTML tag: type <section> and </section> is inserted automatically. Void elements (br, img, etc.) skipped.
    • Emmet abbreviation expansion via @vscode/emmet-helper — type div.card>p#hello in an HTML region and Ctrl-Space (or Tab on accept) expands it to the full HTML.
  • Diagnostics (v0.3). The extension runs three validators per file, debounced at 300ms, and publishes real red/yellow squiggles on the PL/SQL source:

    • CSS via vscode-css-languageservice — unknown properties, parse errors, vendor-prefix checks, duplicate properties, etc.
    • HTML via HTMLHint — tag-case, attribute-case, attribute quotes, duplicate id, empty src, etc. Fragment-hostile rules like tag-pair and doctype-first are off by default (enable them in settings if your strings contain whole documents).
    • JS via either acorn (fast syntax-only, default) or eslint — set plsqlWebEmbedded.diagnostics.jsLinter to eslint to get semantic rules (no-const-assign, no-dupe-keys, no-unreachable, use-isnan, etc.) on top of syntax errors. Set to off to disable JS validation entirely. Customise the rule set with plsqlWebEmbedded.diagnostics.eslintRules (e.g. { "no-undef": "error", "no-console": "warn" }) and declare workspace globals with plsqlWebEmbedded.diagnostics.eslintGlobals (e.g. { "apex": "readonly", "jQuery": "readonly" }) so no-undef doesn't false-positive on framework names. Trusted workspaces have their ESLint config auto-loaded. Flat config (eslint.config.{js,mjs,cjs}) is fully supported; legacy formats (.eslintrc.json, .eslintrc.yml/.yaml, package.json#eslintConfig) have their inline rules, globals, and env (via the globals package) extracted — extends, plugins, and overrides are logged as unsupported. VS Code settings still win over the workspace config. Run PL/SQL: Show Loaded Workspace ESLint Config to inspect what was loaded. Toggle off via plsqlWebEmbedded.diagnostics.loadWorkspaceEslintConfig. Each validator is individually toggleable under plsqlWebEmbedded.diagnostics.{html,css,js} in settings.
  • "Show Embedded Regions" command (PL/SQL: Show Embedded Regions in the command palette) that lists every region the scanner detects in the active file — useful for debugging the heuristics on your own code.

  • Format Document (Shift-Alt-F) on the whole .pks/.pkb file (v0.16-v0.17, rebuilt for safety in v0.23):

    • Outer PL/SQL is re-indented by the built-in basic-indent engine (default): structure-aware (BEGIN/END, IF, LOOP, CASE, EXCEPTION, program-unit IS/AS, multi-line call args) and corruption-proof by design — it changes leading whitespace only, never tokens, and lines inside multi-line literals/comments are kept verbatim. The bundled MIT-licensed sql-formatter is available as an opt-in alternative (plsqlWebEmbedded.formatter.plsql: "sql-formatter"); it re-flows whole statements, runs with all string literals masked (so its multi-line q-quote and 1..10 range bugs can't touch your code), and falls back to basic-indent when it can't parse the file.
    • Embedded HTML / CSS regions are formatted via vscode-html-languageservice and vscode-css-languageservice respectively; multi-line results are indented relative to the literal's own line, and a second format pass is a byte-level no-op.
    • Embedded JS regions are formatted via Prettier's standalone build with the babel parser. A workspace .prettierrc{,.json,.yml,.yaml} or package.json#prettier is auto-discovered (walking up from the document's directory). Per-workspace overrides are layered on top via plsqlWebEmbedded.formatter.prettierConfigOverrides. Each layer is individually toggleable under plsqlWebEmbedded.formatter.{plsql,embedded,js}.
    • Safety rails: in regular '…' literals the '' escape is unescaped before and re-escaped after formatting (so even singleQuote: true Prettier output can't terminate the literal); fragments of '…' || var || '…' concatenations are skipped (reformatting one fragment would change the emitted page, e.g. trim the significant trailing space in '<input type="text" '); and a q-quote region is reverted if formatting would introduce its close sequence.
  • APEX_JAVASCRIPT / APEX_CSS grammar injection (v0.18.2). String args of APEX_JAVASCRIPT.* calls are colorized as JS and APEX_CSS.* as CSS by the TextMate grammar — same path as HTP.* / HTF.* / OWA_UTIL.* already had.

  • Status bar item (v0.18.0). Bottom-right shows $(globe) PL/SQL: N regions when an embedded-content file is active; click it to invoke PL/SQL: Show Embedded Regions.

  • "PL/SQL: Diagnose Setup" command (v0.18.0). Single-shot troubleshooter: prints active editor, language ID, detect options, resolved diagnostics config, host-grammar (xyz.plsql-language) install status, and region count for the active doc. First thing to run if the extension seems silent.

  • First-error toast (v0.18.0). The first time any subsystem (formatter, diagnostics, virtual-doc, eslint-config) logs something matching failed | threw | rejected | error, a one-shot warning toast appears with an "Open Output" button — so silent failures can't hide in the channel.

  • Auto-close < and > (v0.18.0, fixed v0.18.3). Typing < inside an HTML region inserts the matching > for an empty pair; typing > after <tagname … inserts the matching </tagname>. Void elements (br, img, input, …) are skipped, as are self-closing /> tags.

Known lexer limitations

These are deliberately not fixed — each has a one-line reason:

  • PL/SQL '' escapes inside single-quoted strings aren't decoded. HTP.P('<input type=''search''>') puts a literal ' in Oracle's runtime string but the extension extracts the embedded HTML region by raw byte offset and feeds HTMLHint type=''search'' (empty value followed by garbage), which desyncs its parser and silently drops attribute-level diagnostics on that tag. Workaround: use a q-quoted literal — HTP.P(q'[<input type='search'>]') — when the HTML contains apostrophes.
  • CASE / NVL / DECODE arguments aren't treated as chain members. HTP.P(CASE WHEN x THEN '<b>a</b>' ELSE '<i>b</i>' END) — both branches get recognized as HTML regions but share a chainId; validators may flag them as one stream. Fixing needs semantic awareness of conditional PL/SQL constructs.
  • Literals inside nested non-HTML-emitting calls aren't extracted. HTP.P(DECODE(x, 1, '<b>a</b>')) — the literal is at parenDepth > 0 and skipped. Correct for most nested calls (URLENCODE(s) shouldn't treat s as HTML) but wrong for HTML-returning ones. Distinguishing them requires hardcoded package knowledge.
  • Multibyte (emoji/surrogate-pair) q-quote delimiters aren't recognized. Oracle accepts q'😀...😀'; we read only the high surrogate. Rare in practice.
  • Quoted identifiers "HTP"."P"(...) aren't matched. Legal Oracle, vanishingly rare in web PL/SQL.
  • Apostrophe / whitespace q-quote delimiters are accepted by our scanner but rejected by Oracle (q'''...''', q' ... '). Minor spec-drift.
  • Q-quote symmetric-delimiter early-close — q'#foo'#' bar#' closes at the first #'. Oracle documents this as a user footgun.
  • npm install triggers the ESLint config file-watcher (it matches any package.json), causing churn. Cosmetic; config loads idempotent.
  • Grammar over-colorizes APEX_JAVASCRIPT / APEX_CSS args after the first. The region scanner correctly restricts these calls to the first arg only (subsequent args like p_key => 'my-snippet-key' are plain strings, not code) — so diagnostics fire only on the first arg. The TextMate grammar, added in v0.18.2, doesn't model arg position and colorizes ALL string args as the embedded language. Cosmetic only — diagnostics are correct.

What does not work yet

  • Legacy .eslintrc.js / .cjs aren't executed. JS config files with module.exports or export default need dynamic module resolution and often reference require('@eslint-*/...') presets we can't resolve without the workspace's ESLint. Stick to JSON or YAML for legacy.
  • extends / plugins / overrides in legacy configs are ignored. Preset rule sets (eslint:recommended, airbnb-base, etc.) won't apply. Migrate inline or to flat config.
  • Workspace-loaded configs with files patterns are ignored. Entries that target specific globs aren't applied (we don't know the "real path" of PL/SQL-embedded JS fragments). Use a globally-applicable entry with no files to reach our validator.
  • Plugin-based flat configs may fail to load if the plugin packages aren't installed in the workspace's node_modules. Check the PL/SQL Embedded output channel for import errors.
  • No type-level JS checking. The ESLint integration uses the built-in rule set — no TypeScript typecheck, no user-provided .eslintrc is loaded. Rule set is curated for fragment-friendliness.
  • || chaining only follows tagged comments or HTP/HTF/OWA call context. A bare v := '<a>' || var || '</a>'; with no tag still validates each string in isolation. Prefix with /*html*/ to opt into chain stitching. '<div>' || var || '</div>' is tokenized as two separate HTML strings plus a PL/SQL concatenation. That is correct for highlighting but insufficient for parsing the concatenated result as a single HTML document. The tagged-comment tag applies to the string it precedes, not to the whole concatenation chain.
  • Nested function calls inside HTP.P(...) can close the region early. The grammar's end: \) is not bracket-balanced. Real code that does HTP.P(build_fragment(x, y)) will stop HTML treatment at the first ). Use q-quoted literals for anything with inner parens, or use the tagged-comment form outside the call.
  • No real PL/SQL parser. Region detection is heuristic: a tokenizer that knows about strings, line comments, block comments, and HTP./HTF./ OWA_UTIL. call syntax.

Roadmap

The research that preceded this scaffolding identified four rough tiers beyond v0.1:

Tier Capability Effort
v0.1 Highlighting via grammar injection (shipped) —
v0.2 Virtual-document pull-forwarding: completions, hover, go-to-definition inside embedded regions, backed by the built-in HTML/CSS/JS language services (shipped) —
v0.3 Diagnostics via vscode-css-languageservice, HTMLHint, and acorn, with offset mapping back to the PL/SQL document (shipped) —
v0.4 Chain-aware stitching across '…' \|\| expr \|\| '…' inside HTP/HTF/OWA_UTIL calls (shipped) —
v0.4.1 Tagged-comment chain propagation: v := /*html*/ '<a>' \|\| x \|\| '</a>' stitches across the \|\| chain starting from the tagged string (shipped) —
v0.4.2 Diagnostic suppression widened: any diagnostic touching a stitched placeholder char is dropped, killing false positives from placeholder content (shipped) —
v0.5 ESLint via the Node Linter API: semantic rule violations, not just syntax errors (shipped) —
v0.6 Context-aware placeholder characters: 0 digits in CSS value, hex-color, and function-arg positions; x letters elsewhere (shipped) —
v0.7 Proper PL/SQL lexer: top-level string literals are skipped (no false calls from string content), nested HTP/HTF/OWA_UTIL calls get their own chainIds, identifier-skipping replaces regex word-boundary detection (shipped) —
v0.8 User-configurable ESLint rules and globals via VS Code settings (shipped) —
v0.9 Auto-load the workspace's eslint.config.js / .mjs / .cjs flat config; respects workspace trust; watched + reloaded on file change (shipped) —
v0.10 Source-mapped substitution for single-line \|\| gaps (shipped) —
v0.11 Legacy .eslintrc.json / .yml / package.json#eslintConfig with env/globals/rules extraction (shipped) —
v0.12 Arbitrary-length filler for multi-line gaps too (shipped) —
v0.13 Oracle APEX preset (shipped) —
v0.14 Vitest suite (109 cases) + GitHub Actions CI (shipped) —
v0.15 Native-parity sweep — folding, symbols, color picker, links, highlights, signature help, type-def/declaration/impl/refs, linked editing, auto-close tag, Emmet (shipped) —
v0.16 Format Document — sql-formatter for PL/SQL + vscode-html/css-languageservice for embedded regions (shipped) —
v0.17 Prettier (standalone build) for embedded JS regions, with workspace .prettierrc discovery (shipped) —
v0.18 UX shipping pass: extensionDependencies on host grammar, status bar, Diagnose Setup command, first-error toast, auto-close </>, APEX_JAVASCRIPT/CSS grammar injection (shipped v0.18.0–0.18.3) —
v0.19 Marketplace prep: LICENSE, icon, publisher verification, CHANGELOG ~1 day
v0.20 Code actions / quick fixes; rename across virtual URIs ~1 week

The region scanner in src/regionScanner.ts is the kernel of all three follow-on tiers: it already produces (language, kind, contentStart, contentEnd) tuples that the virtual-document provider (v0.2) and diagnostic mapper (v0.3) will consume directly.

Prior art

  • JMAResources.plsql-embedded-languages — closest existing extension; covers highlighting only, does not handle || concatenation.
  • Tobermory.es6-string-html / bierner.comment-tagged-templates — source of the tagged-comment convention used here.
  • microsoft/typescript-lit-html-plugin — canonical interpolation-substitution pattern we plan to adopt for v0.4.
  • microsoft/vscode-html-languageservice, vscode-css-languageservice — the services we plan to embed for v0.3 diagnostics.

Development

npm install
npm run typecheck        # tsc --noEmit
npm test                 # vitest run (68 cases)
npm run build            # esbuild bundle → out/extension.js

CI runs typecheck + test + build on Node 20 and 22 on every push and PR (.github/workflows/ci.yml), and uploads the packaged .vsix as an artifact.

Open the repo in VS Code and press F5 to launch an Extension Development Host. Open samples/dashboard.pks in the host window — you should see HTML, CSS, and JS correctly coloured inside the PL/SQL.

The sample exercises every embedding case the grammar covers. If highlighting looks wrong on real code, paste a fragment into samples/dashboard.pks so the grammar can be regression-tested against it.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft