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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and

## [Unreleased]

## [3.3.0] - 2026-09-08

### Added

- Go to Definition, Find All References, Document Highlight and Rename Symbol for functions, methods, types, enums, enum members, variables, parameters and import aliases. Renaming understands scope and leaves named arguments, import paths and type fields alone; renaming a built-in is refused.
- Offline checks that run as you type, with no network: `missing-version`, `old-version`, `legacy-name`, `local-scope-call`, `unknown-argument`, `duplicate-argument`, `unused-variable`, `unused-parameter` and `unused-import`. Most carry a quick fix. `pinescript.lint.enabled` and `pinescript.lint.disabledRules` control them.
- **Pine Script: Convert to v6**, which applies the version, legacy-name and argument fixes across a whole file and reports exactly what it changed.
- Colour swatches with a picker for hex literals, the built-in colour constants, `color.new()` and `color.rgb()`, writing the result back as a hex literal or an rgb call.
- Semantic highlighting for the identifiers a document declares, so parameters, enum members, type fields and import aliases are coloured for what they are. Both bundled themes gained matching `semanticTokenColors`.

### Changed

- The bundled themes enable semantic highlighting, which they previously switched off.

## [3.2.0] - 2026-09-08

### Added
Expand Down
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<br>
</h1>

<h4 align="center">Completion, hover documentation, signature help, formatting, quick fixes, 97 snippets and themes for TradingView Pine Script® v6 in Visual Studio Code.</h4>
<h4 align="center">Completion, navigation, hover documentation, signature help, formatting, offline checks, colour swatches, 97 snippets and themes for TradingView Pine Script® v6 in Visual Studio Code.</h4>

<p align="center">
<a href="https://marketplace.visualstudio.com/items?itemName=ex-codes.pine-script-syntax-highlighter"><img src="https://vsmarketplacebadges.dev/version-short/ex-codes.pine-script-syntax-highlighter.svg?style=flat-square&label=marketplace&color=blue" alt="Marketplace version"></a>
Expand All @@ -21,7 +21,7 @@

## Why you will like it

Open a `.pine` file and the editor already knows the language: every v6 built-in completes with its signature, hovering anything shows the reference entry, parameter hints follow you through a call, **Format Document** lays the script out the way Pine wants it, and **97 snippets** turn a prefix and <kbd>Tab</kbd> into a full indicator, a strategy exit block or a Bollinger Bands section. Nothing leaves your machine unless you opt in, and the whole thing weighs less than a megabyte.
Open a `.pine` file and the editor already knows the language: every v6 built-in completes with its signature, <kbd>F12</kbd> jumps to your own declarations, <kbd>F2</kbd> renames a symbol everywhere, hovering anything shows the reference entry, **Format Document** lays the script out the way Pine wants it, problems are flagged as you type without your code leaving the machine, colours show a swatch you can click, and **97 snippets** turn a prefix and <kbd>Tab</kbd> into a full indicator, a strategy exit block or a Bollinger Bands section.

## Quick start

Expand All @@ -31,7 +31,8 @@ Open a `.pine` file and the editor already knows the language: every v6 built-in
4. Hover over `input.int`, `ta.crossover` or your own function to read what it does.
5. Try `bb`, `sltp`, `table` or `request.security.tuple` with <kbd>Tab</kbd> to drop in a working building block.
6. Press <kbd>Shift</kbd>+<kbd>Alt</kbd>+<kbd>F</kbd> (<kbd>Shift</kbd>+<kbd>Option</kbd>+<kbd>F</kbd> on macOS) to format the file: blocks get their four spaces, operators and arguments get their spacing, and nothing else moves.
7. Press <kbd>F1</kbd> and type `Pine Script:` to see the commands: new files from templates, docstrings, type annotations and the reference.
7. Put the cursor on one of your own functions and press <kbd>F12</kbd> to jump to it, <kbd>Shift</kbd>+<kbd>F12</kbd> to list every call, or <kbd>F2</kbd> to rename it everywhere at once.
8. Press <kbd>F1</kbd> and type `Pine Script:` to see the commands: new files from templates, docstrings, type annotations, converting an old script to v6, and the reference.

## Features

Expand All @@ -42,6 +43,12 @@ Open a `.pine` file and the editor already knows the language: every v6 built-in
- Named arguments inside a call, `//@` annotations at the start of a comment, and `import` paths that complete user, library and version.
- Function items insert a call and open parameter hints, so a long `strategy.exit()` is a matter of tabbing through its parameters.

### Navigation through your own code

- **Go to Definition** (<kbd>F12</kbd>) on a function, method, type, enum, enum member, variable, parameter or import alias jumps to where you declared it.
- **Find All References** (<kbd>Shift</kbd>+<kbd>F12</kbd>) lists every use, and the occurrences under the cursor are highlighted as you move around.
- **Rename Symbol** (<kbd>F2</kbd>) renames a declaration and every use of it in one edit. It understands scope, so a global `length` is not touched when a function takes its own `length` parameter, and it leaves named arguments, import paths and type fields alone. Renaming a built-in is refused rather than half done.

### Documentation where the cursor is

- **Hover** on a built-in shows the signature, description, parameters, return value and a link to the reference entry. Hover on your own symbol shows its declaration and its `//@` docs. Hover on an `import` line shows a card for the library.
Expand Down Expand Up @@ -90,6 +97,31 @@ Padding used to align a column of assignments is collapsed to one space, which i

Every snippet in this extension and every test fixture has been run through the formatter and back through the TradingView compiler: none of them changed meaning, and formatting twice gives the same file.

### Checks that never leave your machine

Every Pine document is checked as you type, using the reference data that ships with the extension. This is on by default because nothing is sent anywhere; the TradingView compiler is still a separate, opt-in setting.

| Rule | What it catches |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `missing-version` / `old-version` | No `//@version`, or one older than v6. Both offer a fix. |
| `legacy-name` | A bare v4 name that moved into a namespace: `sma`, `security`, `tostring`, `abs`. Offers the v6 name. |
| `local-scope-call` | `plot`, `hline`, `fill`, `bgcolor`, `alertcondition` and friends inside an `if`, `for` or function body, which the compiler rejects. |
| `unknown-argument` | A named argument the function does not take, including options dropped between versions such as `transp`. Suggests the near miss. |
| `duplicate-argument` | The same named argument passed twice. |
| `unused-variable` / `unused-parameter` / `unused-import` | Declarations nothing reads, shown faded rather than as warnings. |

Turn the whole thing off with `pinescript.lint.enabled`, or silence single rules with `pinescript.lint.disabledRules`.

**Pine Script: Convert to v6** applies the version, legacy-name and argument fixes across the whole file in one go, which is most of the work of bringing an old script forward. It says exactly what it changed, including any argument it had to remove, and it is a single undo.

### Colours you can see and click

Every colour a script names gets a swatch in the gutter of the line: hex literals such as `#FF9800`, the built-in constants, `color.new(color.blue, 25)` and `color.rgb(255, 152, 0, 40)`. Click one to open the colour picker; the value is written back as a hex literal or a `color.rgb()` call, transparency included.

### Highlighting that knows your symbols

On top of the grammar, the extension tells the editor which identifiers are yours. Parameters inside a function body, enum members, type fields and import aliases are coloured for what they are rather than guessed at from their spelling. Both bundled themes carry matching colours; other themes pick it up through the standard token types.

### Structure, libraries and diagnostics

- **Outline.** Functions, methods, types with fields, enums with members and top-level variables in the Outline view and breadcrumbs.
Expand Down Expand Up @@ -133,6 +165,7 @@ The grammar and the documentation data are generated from the v6 reference, so v
| Pine Script: Generate Docstring | Inserts or completes `//@function`, `//@param`, `//@returns`, `//@type`, `//@field`, `//@enum` for the declaration at the cursor |
| Pine Script: Add Type Annotations | Prefixes untyped declarations with their inferred type in the selection or the whole file |
| Pine Script: Open Reference | Opens the v6 reference at the built-in under the cursor |
| Pine Script: Convert to v6 | Adds or raises the version pragma and rewrites the v4 names that moved into namespaces |

## Settings

Expand All @@ -145,6 +178,8 @@ The grammar and the documentation data are generated from the v6 reference, so v
| `pinescript.libraries.remote` | `true` | Look up published libraries on TradingView for `import` completion and hover |
| `pinescript.diagnostics.remote` | `false` | Send the document to the TradingView compiler for diagnostics |
| `pinescript.format.enabled` | `true` | Format Document and Format Selection |
| `pinescript.lint.enabled` | `true` | Offline checks; nothing is sent anywhere |
| `pinescript.lint.disabledRules` | `[]` | Rule names the offline checker should skip |

## Privacy

Expand Down Expand Up @@ -172,10 +207,12 @@ src/
reference.json full v6 documentation, generated by scripts/scrape-reference.mjs
extension/
core/ pure TypeScript: tokenizer, document model, completion context,
type inference, docstrings, formatter, quick fixes, templates,
symbol resolution, type inference, docstrings, formatter,
offline rules, quick fixes, colours, semantic tokens, templates,
TradingView client
providers/ VS Code adapters: completion, hover, signature help, symbols,
formatting, code actions, diagnostics, library index
navigation, formatting, colours, semantic tokens, code actions,
diagnostics, offline checks, library index
commands/ command implementations
scripts/
build-grammar.mjs compiles src/ into syntaxes/pinescript.tmLanguage.json and
Expand Down
36 changes: 35 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "pine-script-syntax-highlighter",
"displayName": "Pine Script Syntax Highlighter",
"description": "Pine Script v6 for Visual Studio Code: highlighting, completion, hover documentation, signature help, snippets, themes and compiler diagnostics.",
"version": "3.2.0",
"version": "3.3.0",
"publisher": "ex-codes",
"author": {
"name": "Yankı Küçük",
Expand Down Expand Up @@ -115,6 +115,30 @@
"type": "boolean",
"default": true,
"description": "Format Pine documents on request, indenting blocks with four spaces and spacing operators and arguments."
},
"pinescript.lint.enabled": {
"type": "boolean",
"default": true,
"description": "Check the open document for problems without sending it anywhere: names left over from older versions, calls that only work at the top level, unknown or repeated arguments, and declarations that are never read."
},
"pinescript.lint.disabledRules": {
"type": "array",
"items": {
"type": "string",
"enum": [
"missing-version",
"old-version",
"legacy-name",
"local-scope-call",
"duplicate-argument",
"unknown-argument",
"unused-variable",
"unused-parameter",
"unused-import"
]
},
"default": [],
"description": "Rules the offline checker should not report."
}
}
},
Expand Down Expand Up @@ -144,6 +168,11 @@
{
"command": "pinescript.openReference",
"title": "Pine Script: Open Reference"
},
{
"command": "pinescript.convertToV6",
"title": "Pine Script: Convert to v6",
"enablement": "editorLangId == pinescript"
}
],
"menus": {
Expand All @@ -162,6 +191,11 @@
"command": "pinescript.openReference",
"when": "editorLangId == pinescript",
"group": "1_pinescript@3"
},
{
"command": "pinescript.convertToV6",
"when": "editorLangId == pinescript",
"group": "1_pinescript@4"
}
]
},
Expand Down
57 changes: 57 additions & 0 deletions src/extension/commands/convert-to-v6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as vscode from 'vscode';

import { lint, TARGET_VERSION, type LintIssue } from '../core/lint';
import type { ReferenceIndex } from '../core/reference';
import { analyze } from '../vscode/document-cache';

/** The rules whose fixes together move a script from an older Pine version to the current one. */
const MIGRATION_RULES = new Set(['missing-version', 'old-version', 'legacy-name', 'unknown-argument']);

export function registerConvertToV6(context: vscode.ExtensionContext, ref: ReferenceIndex): void {
context.subscriptions.push(
vscode.commands.registerCommand('pinescript.convertToV6', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.languageId !== 'pinescript') return;
const document = editor.document;
const issues = lint(analyze(document), ref).filter((i) => MIGRATION_RULES.has(i.rule) && i.fix);
if (!issues.length) {
void vscode.window.showInformationMessage(`This script already reads as Pine Script v${TARGET_VERSION}.`);
return;
}
const edit = new vscode.WorkspaceEdit();
for (const issue of issues) {
for (const change of issue.fix!.edits) {
edit.replace(
document.uri,
new vscode.Range(change.startLine, change.startCol, change.endLine, change.endCol),
change.newText,
);
}
}
const applied = await vscode.workspace.applyEdit(edit);
if (!applied) {
void vscode.window.showWarningMessage('The conversion could not be applied.');
return;
}
void vscode.window.showInformationMessage(`Converted to v${TARGET_VERSION}: ${summarise(issues)}.`);
}),
);
}

/** Says plainly what the command changed, since one of its fixes deletes an argument. */
function summarise(issues: LintIssue[]): string {
const count = (rule: string) => issues.filter((i) => i.rule === rule).length;
const removed = issues.filter((i) => i.rule === 'unknown-argument' && i.fix!.title.startsWith('Remove')).length;
const renamedArguments = count('unknown-argument') - removed;
const parts: string[] = [];
if (count('missing-version') || count('old-version')) parts.push('the version pragma');
parts.push(...plural(count('legacy-name'), 'name'));
parts.push(...plural(renamedArguments, 'argument name'));
if (removed) parts.push(`${removed} ${removed === 1 ? 'argument' : 'arguments'} v6 does not accept, removed`);
return parts.join(', ');
}

function plural(count: number, noun: string): string[] {
if (!count) return [];
return [`${count} ${noun}${count === 1 ? '' : 's'}`];
}
Loading
Loading