├── .prettierrc.yaml
├── .gitignore
├── .vscodeignore
├── .gitattributes
├── resources
├── favicon.ico
├── prql-logo.png
├── sql-preview.html
├── sql-preview.css
└── sqlPreview.js
├── docs
└── images
│ ├── prql-vscode.gif
│ ├── prql-vscode.png
│ ├── prql-settings.gif
│ ├── prql-settings.png
│ └── prql-vscode-features.png
├── examples
├── loop.prql
├── fibonacci-numbers.prql
├── .vscode
│ └── settings.json
├── fibonacci-numbers.sql
├── loop.sql
├── pi.prql
└── pi.sql
├── tsconfig.json
├── src
├── views
│ ├── compilationResult.ts
│ ├── viewContext.ts
│ ├── sqlPreviewSerializer.ts
│ └── sqlPreview.ts
├── constants.ts
├── extension.ts
├── diagnostics.ts
├── compiler.ts
└── commands.ts
├── .github
├── dependabot.yml
└── workflows
│ ├── release.yaml
│ └── pull-request.yaml
├── .vscode
└── launch.json
├── language-configuration.json
├── .pre-commit-config.yaml
├── syntaxes
├── inline-prql.json
└── prql.tmLanguage.json
├── eslint.config.mjs
├── CHANGELOG.md
├── package.json
├── README.md
└── LICENSE
/.prettierrc.yaml:
--------------------------------------------------------------------------------
1 | singleQuote: true
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.vsix
3 | out
4 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | .gitignore
4 |
5 | docs/images/**
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behavior to automatically normalize line endings.
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/resources/favicon.ico
--------------------------------------------------------------------------------
/resources/prql-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/resources/prql-logo.png
--------------------------------------------------------------------------------
/docs/images/prql-vscode.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/docs/images/prql-vscode.gif
--------------------------------------------------------------------------------
/docs/images/prql-vscode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/docs/images/prql-vscode.png
--------------------------------------------------------------------------------
/docs/images/prql-settings.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/docs/images/prql-settings.gif
--------------------------------------------------------------------------------
/docs/images/prql-settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/docs/images/prql-settings.png
--------------------------------------------------------------------------------
/examples/loop.prql:
--------------------------------------------------------------------------------
1 | from_text format:json '[{"n": 1 }]'
2 | loop (
3 | select n = n+1
4 | filter n<=3
5 | )
6 |
--------------------------------------------------------------------------------
/docs/images/prql-vscode-features.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PRQL/prql-vscode/HEAD/docs/images/prql-vscode-features.png
--------------------------------------------------------------------------------
/examples/fibonacci-numbers.prql:
--------------------------------------------------------------------------------
1 | from_text format:json '[{"a":1, "b":1}]'
2 | loop (
3 | derive b_new = a + b
4 | select {a=b, b=b_new}
5 | )
6 | take 7
7 |
--------------------------------------------------------------------------------
/examples/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "sqltools.connections": [
3 | {
4 | "previewLimit": 50,
5 | "driver": "DuckDB",
6 | "name": "memory.duckdb",
7 | "database": ":memory:"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es2020",
5 | "lib": ["es2020"],
6 | "outDir": "out",
7 | "sourceMap": true,
8 | "strict": true,
9 | "rootDir": "src"
10 | },
11 | "exclude": ["node_modules"]
12 | }
13 |
--------------------------------------------------------------------------------
/src/views/compilationResult.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * PRQL compilation resust for sql preview and display.
3 | */
4 | export interface CompilationResult {
5 | status: 'ok' | 'error';
6 | sql?: string;
7 | sqlHtml?: string;
8 | error?: {
9 | message: string;
10 | };
11 | lastSqlHtml?: string | undefined;
12 | }
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'npm'
4 | directory: '/'
5 | schedule:
6 | interval: daily
7 | commit-message:
8 | prefix: 'chore: '
9 | # We exclude labels throughout because of https://github.com/dependabot/dependabot-core/issues/7645#issuecomment-1986212847
10 | labels: []
11 |
12 | - package-ecosystem: github-actions
13 | directory: '/'
14 | schedule:
15 | interval: daily
16 | commit-message:
17 | prefix: 'chore: '
18 | labels: []
19 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [released]
4 | workflow_dispatch:
5 |
6 | name: Publish extension
7 | jobs:
8 | publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v6
12 | - uses: actions/setup-node@v6
13 | with:
14 | node-version: 20
15 | - run: npm ci
16 | - uses: HaaLeo/publish-vscode-extension@v2
17 | with:
18 | pat: ${{ secrets.VS_MARKETPLACE_TOKEN }}
19 | registryUrl: https://marketplace.visualstudio.com
20 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that launches the extension inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"]
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/src/views/viewContext.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention */
2 | /**
3 | * PRQL view context keys enum for when clauses and PRQL view menu commands.
4 | *
5 | * @see https://code.visualstudio.com/api/references/when-clause-contexts#add-a-custom-when-clause-context
6 | * @see https://code.visualstudio.com/api/references/when-clause-contexts#inspect-context-keys-utility
7 | */
8 | export const enum ViewContext {
9 | SqlPreviewActive = 'prql.sqlPreviewActive',
10 | LastActivePrqlDocumentUri = 'prql.lastActivePrqlDocumentUri',
11 | }
12 |
--------------------------------------------------------------------------------
/examples/fibonacci-numbers.sql:
--------------------------------------------------------------------------------
1 | WITH table_0 AS (
2 | SELECT
3 | 1 AS a,
4 | 1 AS b
5 | ),
6 | table_4 AS (
7 | WITH RECURSIVE loop AS (
8 | SELECT
9 | a,
10 | b
11 | FROM
12 | table_0 AS table_1
13 | UNION
14 | ALL
15 | SELECT
16 | b AS _expr_0,
17 | a + b
18 | FROM
19 | loop AS table_2
20 | )
21 | SELECT
22 | *
23 | FROM
24 | loop
25 | )
26 | SELECT
27 | a,
28 | b
29 | FROM
30 | table_4 AS table_3
31 | LIMIT
32 | 7
33 |
34 | -- Generated by PRQL compiler version:0.6.1 target:sql.duckdb (https://prql-lang.org)
35 |
--------------------------------------------------------------------------------
/language-configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "comments": {
3 | "lineComment": "#"
4 | },
5 | // symbols used as brackets
6 | "brackets": [
7 | ["[", "]"],
8 | ["(", ")"],
9 | ["{", "}"]
10 | ],
11 | // symbols that are auto closed when typing
12 | "autoClosingPairs": [
13 | ["[", "]"],
14 | ["(", ")"],
15 | ["{", "}"],
16 | ["\"", "\""],
17 | ["'", "'"]
18 | ],
19 | // symbols that can be used to surround a selection
20 | "surroundingPairs": [
21 | ["[", "]"],
22 | ["(", ")"],
23 | ["{", "}"],
24 | ["\"", "\""],
25 | ["'", "'"]
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/examples/loop.sql:
--------------------------------------------------------------------------------
1 | WITH table_0 AS (
2 | SELECT
3 | 1 AS n
4 | ),
5 | table_6 AS (
6 | WITH RECURSIVE loop AS (
7 | SELECT
8 | n
9 | FROM
10 | table_0 AS table_1
11 | UNION
12 | ALL
13 | SELECT
14 | _expr_0
15 | FROM
16 | (
17 | SELECT
18 | n + 1 AS _expr_0
19 | FROM
20 | loop AS table_2
21 | ) AS table_3
22 | WHERE
23 | _expr_0 <= 3
24 | )
25 | SELECT
26 | *
27 | FROM
28 | loop
29 | )
30 | SELECT
31 | n
32 | FROM
33 | table_6 AS table_5
34 |
35 | -- Generated by PRQL compiler version:0.6.1 target:sql.duckdb (https://prql-lang.org)
36 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: trailing-whitespace
6 | - id: end-of-file-fixer
7 | - id: check-yaml
8 | - id: mixed-line-ending
9 | - repo: https://github.com/crate-ci/typos
10 | rev: v1
11 | hooks:
12 | - id: typos
13 | # https://github.com/crate-ci/typos/issues/347
14 | pass_filenames: false
15 | - repo: https://github.com/pre-commit/mirrors-prettier
16 | rev: v4.0.0-alpha.8
17 | hooks:
18 | - id: prettier
19 | additional_dependencies:
20 | - prettier
21 | ci:
22 | # Currently network access isn't supported in the CI product.
23 | autoupdate_commit_msg: 'chore: pre-commit autoupdate'
24 |
--------------------------------------------------------------------------------
/resources/sql-preview.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 | PRQL SQL Preview
16 |
17 |
18 | Waiting for a PRQL file...
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yaml:
--------------------------------------------------------------------------------
1 | # Inspired by https://github.com/prql/prql/blob/663af5e1f166d9058ff88977cd7531b56b0ded1c/.github/workflows/pull-request.yaml
2 |
3 | on:
4 | pull_request:
5 | types: [opened, reopened, synchronize, labeled]
6 | push:
7 | branches:
8 | - main
9 |
10 | concurrency:
11 | group: ${{ github.workflow }}-${{ github.ref }}-${{ github.job }}
12 | cancel-in-progress: true
13 |
14 | jobs:
15 | build:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v6
20 | - uses: actions/setup-node@v6
21 | with:
22 | node-version: 20
23 | - run: npm ci
24 | - run: npm run compile
25 |
26 | automerge-dependabot:
27 | runs-on: ubuntu-latest
28 |
29 | permissions:
30 | pull-requests: write
31 | contents: write
32 |
33 | steps:
34 | - uses: fastify/github-action-merge-dependabot@v3
35 | with:
36 | github-token: ${{ github.token }}
37 | use-github-auto-merge: true
38 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/naming-convention */
2 |
3 | /**
4 | * PRQL extension constants.
5 | */
6 |
7 | // Extension constants
8 | export const PublisherId = 'PRQL-lang';
9 | export const ExtensionName = 'prql-vscode';
10 | export const ExtensionId = 'prql';
11 | export const ExtensionDisplayName = 'PRQL';
12 |
13 | // PRQL webview constants
14 | export const SqlPreviewPanel = `${ExtensionId}.sqlPreviewPanel`;
15 | export const SqlPreviewTitle = 'SQL Preview';
16 |
17 | // PRQL extension command constants
18 | export const OpenSqlPreview = `${ExtensionId}.openSqlPreview`;
19 | export const GenerateSqlFile = `${ExtensionId}.generateSqlFile`;
20 | export const CopySqlToClipboard = `${ExtensionId}.copySqlToClipboard`;
21 | export const ViewSettings = `${ExtensionId}.viewSettings`;
22 |
23 | // PRQL extension setting keys
24 | export const AddCompilerSignatureComment = 'addCompilerSignatureComment';
25 | export const AddTargetDialectToSqlFilenames = 'addTargetDialectToSqlFilenames';
26 |
27 | // VSCodes actions and commands
28 | export const WorkbenchActionOpenSettings = 'workbench.action.openSettings';
29 |
--------------------------------------------------------------------------------
/syntaxes/inline-prql.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileTypes": [],
3 | "injectionSelector": "L:source.js -comment -(string -meta.embedded), L:source.jsx -comment -(string -meta.embedded), L:source.js.jsx -comment -(string -meta.embedded), L:source.ts -comment -(string -meta.embedded), L:source.tsx -comment -(string -meta.embedded)",
4 | "patterns": [
5 | {
6 | "name": "string.js.taggedTemplate.commentTaggedTemplate.prql",
7 | "contentName": "meta.embedded.block.prql",
8 | "begin": "(?x)(\\b(?:\\w+\\.)*(?:prql)\\s*)(`)",
9 | "beginCaptures": {
10 | "1": {
11 | "name": "entity.name.function.tagged-template.js"
12 | },
13 | "2": {
14 | "name": "punctuation.definition.string.template.begin.js"
15 | }
16 | },
17 | "end": "(`)",
18 | "endCaptures": {
19 | "0": {
20 | "name": "string.js"
21 | },
22 | "1": {
23 | "name": "punctuation.definition.string.template.end.js"
24 | }
25 | },
26 | "patterns": [
27 | {
28 | "include": "source.prql"
29 | },
30 | {
31 | "match": "."
32 | }
33 | ]
34 | }
35 | ],
36 | "scopeName": "inline.prql"
37 | }
38 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { window, ExtensionContext } from 'vscode';
2 |
3 | import { SqlPreviewSerializer } from './views/sqlPreviewSerializer';
4 | import { activateDiagnostics } from './diagnostics';
5 | import { registerCommands } from './commands';
6 | import { SqlPreview } from './views/sqlPreview';
7 |
8 | /**
9 | * Activates PRQL extension,
10 | * enables PRQL text document diagnostics,
11 | * registers Open SQL Preview and other
12 | * PRQL extension commands.
13 | *
14 | * @param context Extension context.
15 | */
16 | export function activate(context: ExtensionContext) {
17 | activateDiagnostics(context);
18 | registerCommands(context);
19 |
20 | // register sql preview serializer for restore on vscode reload
21 | context.subscriptions.push(SqlPreviewSerializer.register(context));
22 |
23 | // add active text editor change handler
24 | context.subscriptions.push(
25 | window.onDidChangeActiveTextEditor((editor) => {
26 | if (editor && editor.document.languageId === 'prql') {
27 | // reveal the corresponding sql preview, if already open
28 | SqlPreview.reveal(context, editor.document.uri);
29 | } else {
30 | SqlPreview.clearActiveSqlPreviewContext(context);
31 | }
32 | }),
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import typescriptEslint from '@typescript-eslint/eslint-plugin';
2 | import tsParser from '@typescript-eslint/parser';
3 | import path from 'node:path';
4 | import { fileURLToPath } from 'node:url';
5 | import js from '@eslint/js';
6 | import { FlatCompat } from '@eslint/eslintrc';
7 |
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 | const compat = new FlatCompat({
11 | baseDirectory: __dirname,
12 | recommendedConfig: js.configs.recommended,
13 | allConfig: js.configs.all,
14 | });
15 |
16 | export default [
17 | {
18 | ignores: ['**/out', '**/resources', 'eslint.config.mjs'],
19 | },
20 | ...compat.extends(
21 | 'eslint:recommended',
22 | 'plugin:@typescript-eslint/recommended',
23 | ),
24 | {
25 | plugins: {
26 | '@typescript-eslint': typescriptEslint,
27 | },
28 |
29 | languageOptions: {
30 | parser: tsParser,
31 | ecmaVersion: 5,
32 | sourceType: 'module',
33 | },
34 |
35 | rules: {
36 | '@typescript-eslint/no-explicit-any': 'off',
37 | 'eol-last': ['error', 'always'],
38 | '@typescript-eslint/naming-convention': 'warn',
39 | curly: 'warn',
40 | eqeqeq: 'warn',
41 | 'no-throw-literal': 'warn',
42 | '@typescript-eslint/no-unused-vars': [
43 | 'error',
44 | {
45 | argsIgnorePattern: '^_',
46 | },
47 | ],
48 | },
49 | },
50 | ];
51 |
--------------------------------------------------------------------------------
/src/views/sqlPreviewSerializer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Disposable,
3 | ExtensionContext,
4 | WebviewPanel,
5 | WebviewPanelSerializer,
6 | Uri,
7 | window,
8 | } from 'vscode';
9 |
10 | import { SqlPreview } from './sqlPreview';
11 | import * as constants from '../constants';
12 |
13 | /**
14 | * Sql Preview webview panel serializer for restoring open Sql Previews on vscode reload.
15 | */
16 | export class SqlPreviewSerializer implements WebviewPanelSerializer {
17 | /**
18 | * Registers Sql Preview serializer.
19 | *
20 | * @param context Extension context.
21 | * @returns Disposable object for this webview panel serializer.
22 | */
23 | public static register(context: ExtensionContext): Disposable {
24 | return window.registerWebviewPanelSerializer(
25 | constants.SqlPreviewPanel,
26 | new SqlPreviewSerializer(context),
27 | );
28 | }
29 |
30 | /**
31 | * Creates new Sql Preview webview serializer.
32 | *
33 | * @param extensionUri Extension context.
34 | */
35 | constructor(private readonly context: ExtensionContext) {}
36 |
37 | /**
38 | * Restores open Sql Preview webview panel on vscode reload.
39 | *
40 | * @param webviewPanel Webview panel to restore.
41 | * @param state Saved web view panel state with preview PRQL document Url.
42 | */
43 | async deserializeWebviewPanel(webviewPanel: WebviewPanel, state: any) {
44 | const documentUri: Uri = Uri.file(state.documentUrl);
45 | SqlPreview.render(this.context, documentUri, webviewPanel);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/resources/sql-preview.css:
--------------------------------------------------------------------------------
1 | @media (prefers-color-scheme: light) {
2 | .shiki.dark-plus {
3 | display: none;
4 | }
5 | }
6 |
7 | @media (prefers-color-scheme: dark) {
8 | .shiki.light-plus {
9 | display: none;
10 | }
11 | }
12 |
13 | :root {
14 | --one-color: var(--vscode-focusBorder);
15 | --shiki-color-text: var(--vscode-editor-foreground);
16 | --shiki-color-background: var(--vscode-editor-background);
17 | --shiki-token-constant: var(--one-color);
18 | --shiki-token-string: var(--one-color);
19 | --shiki-token-comment: var(--one-color);
20 | --shiki-token-keyword: var(--one-color);
21 | --shiki-token-parameter: var(--one-color);
22 | --shiki-token-function: var(--one-color);
23 | --shiki-token-string-expression: var(--one-color);
24 | --shiki-token-punctuation: var(--one-color);
25 | --shiki-token-link: var(--one-color);
26 | }
27 |
28 | body,
29 | code {
30 | font-family: var(--vscode-editor-font-family) !important;
31 | font-size: var(--vscode-editor-font-size) !important;
32 | font-weight: var(--vscode-editor-font-weight) !important;
33 | }
34 |
35 | body {
36 | margin: 0px;
37 | padding: 10px;
38 | }
39 |
40 | h3 {
41 | margin: 10px 0 0 10px;
42 | color: var(--vscode-editorError-foreground);
43 | }
44 |
45 | .error {
46 | padding: 0px;
47 | margin: 0px;
48 | }
49 |
50 | #result {
51 | margin: 0px;
52 | padding: 0px;
53 | }
54 |
55 | #last-html {
56 | overflow-y: scroll;
57 | max-height: 80vh;
58 | margin: 0px;
59 | padding: 0px;
60 | }
61 |
62 | #error-container {
63 | overflow-x: scroll;
64 | color: var(--vscode-editorError-foreground);
65 | background-color: var(--vscode-editor-background);
66 | }
67 |
68 | .error-container-fixed {
69 | left: 10px;
70 | right: 10px;
71 | bottom: 0;
72 | position: fixed;
73 | border-top: 2px solid var(--vscode-editorError-foreground);
74 | }
75 |
76 | #error-message {
77 | padding: 0px;
78 | }
79 |
--------------------------------------------------------------------------------
/src/diagnostics.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Diagnostic,
3 | DiagnosticCollection,
4 | DiagnosticSeverity,
5 | ExtensionContext,
6 | Position,
7 | Range,
8 | TextDocument,
9 | languages,
10 | window,
11 | workspace,
12 | } from 'vscode';
13 |
14 | import { SourceLocation, compile } from './compiler';
15 |
16 | function getRange(location: SourceLocation | null): Range {
17 | if (location) {
18 | return new Range(
19 | location.start[0],
20 | location.start[1],
21 | location.end[0],
22 | location.end[1],
23 | );
24 | }
25 | return new Range(new Position(0, 0), new Position(0, 0));
26 | }
27 |
28 | function updateLineDiagnostics(diagnosticCollection: DiagnosticCollection) {
29 | const editor = window.activeTextEditor;
30 |
31 | if (editor && editor.document.languageId === 'prql') {
32 | const text = editor.document.getText();
33 | const result = compile(text);
34 |
35 | if (!Array.isArray(result)) {
36 | // success, clear the errors
37 | diagnosticCollection.set(editor.document.uri, []);
38 | } else {
39 | const diagnostics = result
40 | // don't report errors for missing main pipeline
41 | .filter((e) => e.code !== 'E0001')
42 | .map((e) => {
43 | return new Diagnostic(
44 | getRange(e.location),
45 | e.reason,
46 | DiagnosticSeverity.Error,
47 | );
48 | });
49 |
50 | diagnosticCollection.set(editor.document.uri, diagnostics);
51 | }
52 | }
53 | }
54 |
55 | export function activateDiagnostics(context: ExtensionContext) {
56 | const diagnosticCollection = languages.createDiagnosticCollection('prql');
57 | context.subscriptions.push(diagnosticCollection);
58 |
59 | workspace.onDidCloseTextDocument((document: TextDocument) =>
60 | diagnosticCollection.set(document.uri, []),
61 | );
62 |
63 | [
64 | workspace.onDidOpenTextDocument,
65 | workspace.onDidChangeTextDocument,
66 | window.onDidChangeActiveTextEditor,
67 | ].forEach((event) => {
68 | context.subscriptions.push(
69 | event(() => updateLineDiagnostics(diagnosticCollection)),
70 | );
71 | });
72 |
73 | updateLineDiagnostics(diagnosticCollection);
74 | }
75 |
--------------------------------------------------------------------------------
/src/compiler.ts:
--------------------------------------------------------------------------------
1 | import { workspace, WorkspaceConfiguration } from 'vscode';
2 |
3 | import * as prql from 'prqlc';
4 | import * as constants from './constants';
5 |
6 | export function compile(prqlString: string): string | ErrorMessage[] {
7 | // get prql settings
8 | const prqlSettings: WorkspaceConfiguration =
9 | workspace.getConfiguration('prql');
10 | const target = prqlSettings.get('target');
11 | const addCompilerInfo = (
12 | prqlSettings.get(constants.AddCompilerSignatureComment)
13 | );
14 |
15 | // create compile options from prql workspace settings
16 | const compileOptions = new prql.CompileOptions();
17 | compileOptions.signature_comment = addCompilerInfo;
18 | if (target !== 'Any') {
19 | compileOptions.target = `sql.${target.toLowerCase()}`;
20 | } else {
21 | compileOptions.target = 'sql.any';
22 | }
23 |
24 | try {
25 | // run prql compile
26 | return prql.compile(prqlString, compileOptions) as string;
27 | } catch (error) {
28 | if ((error as any)?.message) {
29 | try {
30 | const errorMessages = JSON.parse((error as any).message);
31 | console.log(errorMessages);
32 | return errorMessages.inner as ErrorMessage[];
33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
34 | } catch (_error) {
35 | throw error;
36 | }
37 | }
38 | throw error;
39 | }
40 | }
41 |
42 | export interface ErrorMessage {
43 | /// Message kind. Currently only Error is implemented.
44 | kind: 'Error' | 'Warning' | 'Lint';
45 | /// Machine-readable identifier of the error
46 | code: string | null;
47 | /// Plain text of the error
48 | reason: string;
49 | /// A list of suggestions of how to fix the error
50 | hint: string | null;
51 | /// Character offset of error origin within a source file
52 | span: [number, number] | null;
53 |
54 | /// Annotated code, containing cause and hints.
55 | display: string | null;
56 | /// Line and column number of error origin within a source file
57 | location: SourceLocation | null;
58 | }
59 |
60 | /// Location within the source file.
61 | /// Tuples contain:
62 | /// - line number (0-based),
63 | /// - column number within that line (0-based),
64 | export interface SourceLocation {
65 | start: [number, number];
66 | end: [number, number];
67 | }
68 |
--------------------------------------------------------------------------------
/examples/pi.prql:
--------------------------------------------------------------------------------
1 | prql target:sql.duckdb
2 |
3 | let config = (
4 | from_text format:json '[{"num_digits":50}]'
5 | derive [
6 | array_len = (10*num_digits)/3,
7 | calc_len = 1+4,
8 | loop_len = array_len + calc_len,
9 | ]
10 | )
11 |
12 | func loop_steps step_0 step_i step_1 step_2 step_3 other -> case [
13 | k==0 => step_0,
14 | 1 <= k and k <= array_len => step_i,
15 | k==array_len+1 => step_1,
16 | k==array_len+2 => step_2,
17 | k==array_len+3 => step_3,
18 | true => other,
19 | ]
20 |
21 | func q_steps step_q9 step_q10 step_j2 step_jg2 other -> case [
22 | q==9 => step_q9,
23 | q==10 => step_q10,
24 | j==2 => step_j2,
25 | j>2 => step_jg2,
26 | true => other,
27 | ]
28 |
29 |
30 | from config
31 | select [
32 | num_digits,
33 | array_len,
34 | loop_len,
35 | j = 0,
36 | k = 0,
37 | q = 0,
38 | a = s"[2 for i in range({array_len})]",
39 | nines = 0,
40 | predigit = 0,
41 | output = '',
42 | ]
43 | loop (
44 | filter j < num_digits + 1
45 | derive [
46 | j_new = case [k==0 => j+1, true => j],
47 | k_new = (k+1) % loop_len,
48 | q_step_i = (10*s"{a}[{k}]"+q*(array_len-k+1))/(2*(array_len-k)+1),
49 | q_new = loop_steps 0 q_step_i (q/10) q q q,
50 |
51 | a_step_i = s"[CASE WHEN i=={k} THEN (10*{a}[i]+{q}*({array_len}-i+1))%(2*({array_len}-i)+1) ELSE {a}[i] END for i in generate_series(1,{array_len})]",
52 | a_step_1 = s"[CASE WHEN i=={array_len} THEN {q}%10 ELSE {a}[i] END for i in generate_series(1,{array_len})]",
53 | a_new = loop_steps a a_step_i a_step_1 a a a,
54 |
55 | nines_new = loop_steps nines nines nines (q_steps (nines+1) 0 nines nines nines) (case [q!=9 and q!=10 and nines!=0 => 0, true => nines]) nines,
56 | predigit_new = loop_steps predigit predigit predigit (q_steps predigit 0 q q q) predigit predigit,
57 |
58 | output_step_2 = (q_steps '' s"({predigit}+1)::string || repeat('0', {nines})" s"{predigit}::string || '.'" s"{predigit}::string" ''),
59 | output_step_3 = (case [q!=9 and q!=10 and nines!=0 => s"repeat('9', {nines})", true => '']),
60 | output_new = loop_steps '' '' '' output_step_2 output_step_3 '',
61 | ]
62 | select [
63 | num_digits,
64 | array_len,
65 | loop_len,
66 | j = j_new,
67 | k = k_new,
68 | q = q_new,
69 | a = a_new,
70 | nines = nines_new,
71 | predigit = predigit_new,
72 | output = output_new,
73 | ]
74 | )
75 | aggregate [pi=s"string_agg({output}, '')"]
76 |
--------------------------------------------------------------------------------
/resources/sqlPreview.js:
--------------------------------------------------------------------------------
1 | // initialize vscode api
2 | const vscode = acquireVsCodeApi();
3 |
4 | // prql document vars and view state
5 | let documentUrl = '';
6 | let viewState = { documentUrl: documentUrl };
7 |
8 | // add page load handler
9 | window.addEventListener('load', initializeView);
10 |
11 | /**
12 | * Initializes sql preview webview.
13 | */
14 | function initializeView() {
15 | // restore previous view state
16 | viewState = vscode.getState();
17 | if (viewState && viewState.documentUrl) {
18 | // get last previewed prql document url
19 | documentUrl = viewState.documentUrl;
20 | } else {
21 | // create new empty view config
22 | viewState = {};
23 | viewState.documentUrl = documentUrl;
24 | vscode.setState(viewState);
25 | }
26 |
27 | // request initial sql preview load
28 | vscode.postMessage({ command: 'refresh' });
29 | }
30 |
31 | // add view update handler
32 | window.addEventListener('message', (event) => {
33 | switch (event.data.command) {
34 | case 'update':
35 | // show updated sql preview content
36 | update(event.data.result);
37 | break;
38 | case 'changeTheme':
39 | // do nothing: this webview html is UI theme neutral,
40 | // and will update sql html content on the next edit
41 | break;
42 | case 'refresh':
43 | updateViewState(event.data);
44 | break;
45 | default:
46 | throw new Error('unknown message');
47 | }
48 | });
49 |
50 | /**
51 | * Updates Sql Preview view state on initial view load and refresh.
52 | *
53 | * @param {*} prqlInfo Prql document info from webview.
54 | */
55 | function updateViewState(prqlInfo) {
56 | // get and save prql document url in view state
57 | documentUrl = prqlInfo.documentUrl;
58 | viewState.documentUrl = documentUrl;
59 | vscode.setState(viewState);
60 | }
61 |
62 | /**
63 | * Displays updated sql html from compiled PRQL result.
64 | *
65 | * @param {*} compilationResult PRQL compilation result.
66 | */
67 | function update(compilationResult) {
68 | // show updated sql preview content
69 | const result = compilationResult;
70 | const errorContainer = document.getElementById('error-container');
71 | if (result.status === 'ok') {
72 | document.getElementById('result').innerHTML = result.sqlHtml;
73 | } else if (result.lastSqlHtml) {
74 | document.getElementById('last-html').innerHTML = result.lastSqlHtml;
75 | document
76 | .getElementById('error-container')
77 | .classList.add('error-container-fixed');
78 | }
79 |
80 | if (result.status === 'error' && result.error.message.length > 0) {
81 | // show errors
82 | document.getElementById('error-message').innerHTML = result.error.message;
83 | errorContainer.style.display = 'block';
84 | } else {
85 | // hide error container
86 | errorContainer.style.display = 'none';
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/examples/pi.sql:
--------------------------------------------------------------------------------
1 | WITH table_0 AS (
2 | SELECT
3 | 50 AS num_digits
4 | ),
5 | config AS (
6 | SELECT
7 | num_digits,
8 | 10 * num_digits / 3 AS array_len,
9 | 5 AS calc_len,
10 | 10 * num_digits / 3 + 5 AS loop_len
11 | FROM
12 | table_0 AS table_1
13 | ),
14 | table_6 AS (
15 | WITH RECURSIVE loop AS (
16 | SELECT
17 | num_digits,
18 | array_len,
19 | loop_len,
20 | 0 AS _expr_0,
21 | 0 AS _expr_1,
22 | 0 AS _expr_2,
23 | [2 for i in range(array_len)] AS _expr_3,
24 | 0 AS _expr_4,
25 | 0 AS _expr_5,
26 | '' AS _expr_6
27 | FROM
28 | config
29 | UNION
30 | ALL
31 | SELECT
32 | num_digits,
33 | array_len,
34 | loop_len,
35 | _expr_12 AS _expr_15,
36 | _expr_11 AS _expr_16,
37 | _expr_10 AS _expr_17,
38 | _expr_9 AS _expr_18,
39 | _expr_8 AS _expr_19,
40 | _expr_7 AS _expr_20,
41 | CASE
42 | WHEN _expr_1 = 0 THEN ''
43 | WHEN 1 <= _expr_1
44 | AND _expr_1 <= array_len THEN ''
45 | WHEN _expr_1 = array_len + 1 THEN ''
46 | WHEN _expr_1 = array_len + 2 THEN _expr_13
47 | WHEN _expr_1 = array_len + 3 THEN _expr_14
48 | ELSE ''
49 | END
50 | FROM
51 | (
52 | SELECT
53 | num_digits,
54 | array_len,
55 | loop_len,
56 | CASE
57 | WHEN _expr_1 = 0 THEN _expr_5
58 | WHEN 1 <= _expr_1
59 | AND _expr_1 <= array_len THEN _expr_5
60 | WHEN _expr_1 = array_len + 1 THEN _expr_5
61 | WHEN _expr_1 = array_len + 2 THEN CASE
62 | WHEN _expr_2 = 9 THEN _expr_5
63 | WHEN _expr_2 = 10 THEN 0
64 | WHEN _expr_0 = 2 THEN _expr_2
65 | WHEN _expr_0 > 2 THEN _expr_2
66 | ELSE _expr_2
67 | END
68 | WHEN _expr_1 = array_len + 3 THEN _expr_5
69 | ELSE _expr_5
70 | END AS _expr_7,
71 | CASE
72 | WHEN _expr_1 = 0 THEN _expr_4
73 | WHEN 1 <= _expr_1
74 | AND _expr_1 <= array_len THEN _expr_4
75 | WHEN _expr_1 = array_len + 1 THEN _expr_4
76 | WHEN _expr_1 = array_len + 2 THEN CASE
77 | WHEN _expr_2 = 9 THEN _expr_4 + 1
78 | WHEN _expr_2 = 10 THEN 0
79 | WHEN _expr_0 = 2 THEN _expr_4
80 | WHEN _expr_0 > 2 THEN _expr_4
81 | ELSE _expr_4
82 | END
83 | WHEN _expr_1 = array_len + 3 THEN CASE
84 | WHEN _expr_2 <> 9
85 | AND _expr_2 <> 10
86 | AND _expr_4 <> 0 THEN 0
87 | ELSE _expr_4
88 | END
89 | ELSE _expr_4
90 | END AS _expr_8,
91 | CASE
92 | WHEN _expr_1 = 0 THEN _expr_3
93 | WHEN 1 <= _expr_1
94 | AND _expr_1 <= array_len THEN [CASE WHEN i==_expr_1 THEN (10*_expr_3[i] + _expr_2 *(array_len - i + 1)
95 | ) %(2 *(array_len - i) + 1)
96 | ELSE _expr_3 [i]
97 | END for i in generate_series(1, array_len) ]
98 | WHEN _expr_1 = array_len + 1 THEN [CASE WHEN i==array_len THEN _expr_2%10 ELSE _expr_3[i]
99 | END for i in generate_series(1, array_len) ]
100 | WHEN _expr_1 = array_len + 2 THEN _expr_3
101 | WHEN _expr_1 = array_len + 3 THEN _expr_3
102 | ELSE _expr_3
103 | END AS _expr_9,
104 | CASE
105 | WHEN _expr_1 = 0 THEN 0
106 | WHEN 1 <= _expr_1
107 | AND _expr_1 <= array_len THEN (
108 | 10 * _expr_3 [_expr_1] + _expr_2 * (array_len - _expr_1 + 1)
109 | ) / (2 * (array_len - _expr_1) + 1)
110 | WHEN _expr_1 = array_len + 1 THEN _expr_2 / 10
111 | WHEN _expr_1 = array_len + 2 THEN _expr_2
112 | WHEN _expr_1 = array_len + 3 THEN _expr_2
113 | ELSE _expr_2
114 | END AS _expr_10,
115 | (_expr_1 + 1) % loop_len AS _expr_11,
116 | CASE
117 | WHEN _expr_1 = 0 THEN _expr_0 + 1
118 | ELSE _expr_0
119 | END AS _expr_12,
120 | _expr_1,
121 | CASE
122 | WHEN _expr_2 = 9 THEN ''
123 | WHEN _expr_2 = 10 THEN (_expr_5 + 1) :: string || repeat('0', _expr_4)
124 | WHEN _expr_0 = 2 THEN _expr_5 :: string || '.'
125 | WHEN _expr_0 > 2 THEN _expr_5 :: string
126 | ELSE ''
127 | END AS _expr_13,
128 | CASE
129 | WHEN _expr_2 <> 9
130 | AND _expr_2 <> 10
131 | AND _expr_4 <> 0 THEN repeat('9', _expr_4)
132 | ELSE ''
133 | END AS _expr_14,
134 | _expr_2,
135 | _expr_4
136 | FROM
137 | loop AS table_2
138 | WHERE
139 | _expr_0 < num_digits + 1
140 | ) AS table_3
141 | )
142 | SELECT
143 | *
144 | FROM
145 | loop
146 | )
147 | SELECT
148 | string_agg(_expr_6, '') AS pi
149 | FROM
150 | table_6 AS table_5
151 |
152 | -- Generated by PRQL compiler version:0.6.1 target:sql.duckdb (https://prql-lang.org)
153 |
--------------------------------------------------------------------------------
/syntaxes/prql.tmLanguage.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
3 | "name": "PRQL",
4 | "scopeName": "source.prql",
5 | "fileTypes": ["prql"],
6 | "patterns": [
7 | {
8 | "include": "#unicode-bidi"
9 | },
10 | {
11 | "include": "#docblock"
12 | },
13 | {
14 | "include": "#comment"
15 | },
16 | {
17 | "include": "#constants"
18 | },
19 | {
20 | "include": "#datatypes"
21 | },
22 | {
23 | "include": "#keywords"
24 | },
25 | {
26 | "include": "#keyword-operator"
27 | },
28 | {
29 | "include": "#named-args"
30 | },
31 | {
32 | "include": "#assigns"
33 | },
34 | {
35 | "include": "#function-call"
36 | },
37 | {
38 | "include": "#type-def"
39 | },
40 | {
41 | "include": "#interpolation-strings"
42 | },
43 | {
44 | "include": "#string-quoted-raw-single"
45 | },
46 | {
47 | "include": "#string-quoted-raw-double"
48 | },
49 | {
50 | "include": "#string-quoted-triple"
51 | },
52 | {
53 | "include": "#string-quoted-single"
54 | },
55 | {
56 | "include": "#string-quoted-double"
57 | },
58 | {
59 | "include": "#time-units"
60 | },
61 | {
62 | "include": "#ident"
63 | }
64 | ],
65 | "repository": {
66 | "docblock": {
67 | "name": "comment.block.documentation",
68 | "match": "#!.*$"
69 | },
70 | "comment": {
71 | "name": "comment.line.number-sign",
72 | "match": "#.*$"
73 | },
74 | "constants": {
75 | "name": "constant.language",
76 | "match": "true|false|null"
77 | },
78 | "datatypes": {
79 | "name": "storage.type",
80 | "match": "(bool|int8|int16|int32|int64|int128|int|float|text|set)\\b"
81 | },
82 | "escape": {
83 | "name": "constant.character.escape",
84 | "match": "\\\\."
85 | },
86 | "keywords": {
87 | "patterns": [
88 | {
89 | "name": "keyword.control.prql",
90 | "match": "\\b(let|into|case|prql|type|module|internal|from|from_text|select|derive|filter|take|sort|join|aggregate|group|null|true|false)\\b"
91 | }
92 | ]
93 | },
94 | "keyword-operator": {
95 | "name": "keyword.operator",
96 | "match": "==|~=|\\+|-|\\*|!=|->|=>|<=|>=|&&|<|>"
97 | },
98 | "time-units": {
99 | "name": "keyword.other.unit",
100 | "match": "years|months|weeks|days|hours|minutes|seconds|milliseconds|microseconds"
101 | },
102 | "named-args": {
103 | "match": "(\\w+)\\s*:",
104 | "captures": {
105 | "1": { "name": "entity.name.tag" }
106 | }
107 | },
108 | "assigns": {
109 | "match": "(\\w+)\\s*=(?!=)",
110 | "captures": {
111 | "1": { "name": "variable.name" }
112 | }
113 | },
114 | "function-call": {
115 | "match": "\\b(\\w+)\\b(\\s+(\\w|[.])+)+(\\s|$|,|]|\\))",
116 | "captures": {
117 | "1": {
118 | "name": "support.function"
119 | },
120 | "2": {
121 | "name": "variable.parameter"
122 | }
123 | }
124 | },
125 | "type-def": {
126 | "name": "support.type",
127 | "match": "<\\w+>"
128 | },
129 | "ident": {
130 | "name": "variable",
131 | "match": "\\b(\\w+)\\b"
132 | },
133 | "interpolation-strings": {
134 | "name": "string.interpolated",
135 | "begin": "(s|f)\"",
136 | "end": "\"",
137 | "patterns": [
138 | {
139 | "name": "keyword.operator.new",
140 | "match": "\\{[^}]*}"
141 | },
142 | {
143 | "include": "#escape"
144 | }
145 | ]
146 | },
147 | "unicode-bidi": {
148 | "name": "invalid.illegal",
149 | "match": "(\u202A|\u202B|\u202D|\u202E|\u2066|\u2067|\u2068|\u202C|\u2069)"
150 | },
151 | "string-quoted-raw-single": {
152 | "name": "string.quoted",
153 | "begin": "r'",
154 | "end": "'"
155 | },
156 | "string-quoted-raw-double": {
157 | "name": "string.quoted",
158 | "begin": "r\"",
159 | "end": "\""
160 | },
161 | "string-quoted-single": {
162 | "name": "string.quoted",
163 | "begin": "'",
164 | "end": "'",
165 | "patterns": [
166 | {
167 | "include": "#escape"
168 | }
169 | ]
170 | },
171 | "string-quoted-double": {
172 | "name": "string.quoted",
173 | "begin": "\"",
174 | "end": "\"",
175 | "patterns": [
176 | {
177 | "include": "#escape"
178 | }
179 | ]
180 | },
181 | "string-quoted-triple": {
182 | "name": "string.quoted",
183 | "begin": "\"\"\"",
184 | "end": "\"\"\"",
185 | "patterns": [
186 | {
187 | "include": "#escape"
188 | }
189 | ]
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/commands.ts:
--------------------------------------------------------------------------------
1 | import {
2 | commands,
3 | env,
4 | window,
5 | workspace,
6 | Disposable,
7 | ExtensionContext,
8 | Uri,
9 | } from 'vscode';
10 |
11 | import * as fs from 'fs';
12 | import * as path from 'path';
13 |
14 | import { SqlPreview } from './views/sqlPreview';
15 | import { TextEncoder } from 'util';
16 | import { compile } from './compiler';
17 | import * as constants from './constants';
18 |
19 | /**
20 | * Registers PRQL extension commands.
21 | *
22 | * @param context Extension context.
23 | */
24 | export function registerCommands(context: ExtensionContext) {
25 | registerCommand(context, constants.ViewSettings, viewPrqlSettings);
26 |
27 | registerCommand(context, constants.GenerateSqlFile, (documentUri: Uri) => {
28 | const editor = window.activeTextEditor;
29 | if (!documentUri && editor && editor.document.languageId === 'prql') {
30 | // use prql from the active prql text editor
31 | generateSqlFile(editor.document.uri, editor.document.getText());
32 | } else if (
33 | documentUri &&
34 | fs.existsSync(documentUri.fsPath) &&
35 | documentUri.fsPath.endsWith('.prql')
36 | ) {
37 | // load prql code from the local prql file
38 | const prqlCode: string = fs.readFileSync(documentUri.fsPath, 'utf8');
39 | generateSqlFile(documentUri, prqlCode);
40 | }
41 | });
42 |
43 | registerCommand(context, constants.OpenSqlPreview, (documentUri: Uri) => {
44 | if (!documentUri && window.activeTextEditor) {
45 | // use active text editor document Uri
46 | documentUri = window.activeTextEditor.document.uri;
47 | }
48 |
49 | // render Sql Preview for the requested PRQL document
50 | SqlPreview.render(context, documentUri);
51 | });
52 |
53 | registerCommand(context, constants.CopySqlToClipboard, () => {
54 | // get last generated prql sql content from workspace state
55 | let sql: string | undefined = context.workspaceState.get('prql.sql');
56 |
57 | let sqlFileName = 'SQL';
58 | if (SqlPreview.currentView) {
59 | // get sql filename and content fromn sql preview
60 | sqlFileName = `prql://${path.basename(
61 | SqlPreview.currentView.documentUri.path,
62 | '.prql',
63 | )}.sql`;
64 | sql = SqlPreview.currentView.lastCompilationResult?.sql;
65 | }
66 |
67 | if (sql !== undefined) {
68 | // write the last active sql preview sql code to vscode clipboard
69 | env.clipboard.writeText(sql);
70 | window.showInformationMessage(`Copied ${sqlFileName} to Clipboard.`);
71 | }
72 | });
73 | }
74 |
75 | /**
76 | * Registers vscode extension command.
77 | *
78 | * @param context Extension context.
79 | * @param commandId Command identifier.
80 | * @param callback Command handler.
81 | * @param thisArg The `this` context used when invoking command handler.
82 | */
83 | function registerCommand(
84 | context: ExtensionContext,
85 | commandId: string,
86 | callback: (...args: any[]) => any,
87 | thisArg?: any,
88 | ): void {
89 | const command: Disposable = commands.registerCommand(
90 | commandId,
91 | async (...args) => {
92 | try {
93 | await callback(...args);
94 | } catch (e: unknown) {
95 | window.showErrorMessage(String(e));
96 | console.error(e);
97 | }
98 | },
99 | thisArg,
100 | );
101 | context.subscriptions.push(command);
102 | }
103 |
104 | /**
105 | * Opens vscode Settings panel with PRQL settings.
106 | */
107 | async function viewPrqlSettings() {
108 | await commands.executeCommand(
109 | constants.WorkbenchActionOpenSettings,
110 | constants.ExtensionId,
111 | );
112 | }
113 |
114 | /**
115 | * Compiles PRQL text for the given PRQL document uri, and creates
116 | * or updates the corresponding SQL file with PRQL compiler output.
117 | *
118 | * Opens generated SQL file in text editor for code formatting,
119 | * or running generated SQL statements with available
120 | * vscode database extensions and sql tools.
121 | *
122 | * @param prqlDocumentUri PRQL source document Uri.
123 | * @param prqlCode PRQL source code.
124 | */
125 | async function generateSqlFile(prqlDocumentUri: Uri, prqlCode: string) {
126 | // compile given prql source code
127 | const sqlCode = compile(prqlCode);
128 |
129 | if (Array.isArray(sqlCode)) {
130 | // display prql compilation errors
131 | window.showErrorMessage(`PRQL Compile \
132 | ${sqlCode[0].display ?? sqlCode[0].reason}`);
133 | } else {
134 | // get sql file generation prql settings
135 | const prqlSettings = workspace.getConfiguration('prql');
136 | const target = prqlSettings.get('target');
137 | const addTargetDialectToSqlFilenames = (
138 | prqlSettings.get(constants.AddTargetDialectToSqlFilenames)
139 | );
140 |
141 | // create sql filename based on prql file path, name, and current settings
142 | const prqlFilePath = path.parse(prqlDocumentUri.fsPath);
143 | let sqlFilenameSuffix = '';
144 | if (
145 | addTargetDialectToSqlFilenames &&
146 | target !== 'Generic' &&
147 | target !== 'Any'
148 | ) {
149 | sqlFilenameSuffix = `.${target.toLowerCase()}`;
150 | }
151 | const sqlFilePath = path.join(
152 | prqlFilePath.dir,
153 | `${prqlFilePath.name}${sqlFilenameSuffix}.sql`,
154 | );
155 |
156 | // create sql file
157 | const sqlFileUri: Uri = Uri.file(sqlFilePath);
158 | const textEncoder: TextEncoder = new TextEncoder();
159 | const sqlContent: Uint8Array = textEncoder.encode(sqlCode);
160 | await workspace.fs.writeFile(sqlFileUri, sqlContent);
161 |
162 | // show generated sql file
163 | await window.showTextDocument(sqlFileUri);
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | Small changes and releases to include recent PRQL versions are not recorded
4 | here. Instead see [releases](https://github.com/PRQL/prql-vscode/releases) for a
5 | brief summary of added extension features, extension source code zip archive,
6 | and `prql-vscode-x.x.x.vsix` extension package download.
7 |
8 | ## 0.13.0 - 2024-07-25
9 |
10 | - Bump to 0.13.0
11 |
12 | ## 0.12.2 - 2024-06-11
13 |
14 | - Bump to 0.12.2
15 | - Change dependency name to `prqlc` (from `prql-js`)
16 |
17 | ## 0.11.3 - 2024-02-12
18 |
19 | - Bump to 0.11.3
20 |
21 | ## 0.9.0
22 |
23 | ### Breaking changes
24 |
25 | - The `Hive` target option is removed, and the `None` target option is renamed to `Any`.
26 |
27 | ## 0.6.0
28 |
29 | - Refactor SQL Preview webview implementation
30 | ([#60](https://github.com/PRQL/prql-vscode/issues/60))
31 | - Add VS Code marketplace badges to README.md
32 | ([#87](https://github.com/PRQL/prql-vscode/issues/87))
33 | - Add PRQL Settings shortcut menu button to SQL Preview and PRQL Editor titlebar
34 | ([#90](https://github.com/PRQL/prql-vscode/issues/90))
35 | - Remove prql-example.png from /resources
36 | ([#91](https://github.com/PRQL/prql-vscode/issues/91))
37 | - Add PRQL compiler signature comment boolean setting to extension config
38 | ([#94](https://github.com/PRQL/prql-vscode/issues/94))
39 | - Document new PRQL Settings under Configuration section in README.md
40 | ([#97](https://github.com/PRQL/prql-vscode/issues/97))
41 | - Change prql.target extension setting default to Generic and add None option
42 | ([#98](https://github.com/PRQL/prql-vscode/issues/98))
43 | - Set PRQL Settings order to show Target setting first
44 | ([#99](https://github.com/PRQL/prql-vscode/issues/99))
45 | - Implement SQL Preview webview deserialize to show it after VS Code reload
46 | ([#102](https://github.com/PRQL/prql-vscode/issues/102))
47 | - Add boolean prql.addTargetDialectToSqlFilenames setting for the generated SQL filenames
48 | ([#103](https://github.com/PRQL/prql-vscode/issues/103))
49 | - Create and use separate SQL Preview webview for multiple open PRQL documents
50 | ([#108](https://github.com/PRQL/prql-vscode/issues/108))
51 | - Display virtual sql filename in clipboard copy notification message
52 | ([#109](https://github.com/PRQL/prql-vscode/issues/109))
53 | - Document new Sql Preview update release v0.6.0 features
54 | ([#111](https://github.com/PRQL/prql-vscode/issues/111))
55 | - Allow to open Sql Preview for a .prql file from a menu in built-in vscode file explorer
56 | ([#113](https://github.com/PRQL/prql-vscode/issues/113))
57 | - Allow to generate SQL file from a PRQL document in vscode file explorer
58 | ([#115](https://github.com/PRQL/prql-vscode/issues/115))
59 | - Add Copy Sql to Clipboard menu option to PRQL editor and Sql Preview editor/title/context menus
60 | ([#116](https://github.com/PRQL/prql-vscode/issues/116))
61 | - Add PRQL Settings menu to PRQL text editor/title/context menus
62 | ([#117](https://github.com/PRQL/prql-vscode/issues/117))
63 | - Update CHANGELOG.md for the v0.6.0 release
64 | ([#120](https://github.com/PRQL/prql-vscode/issues/120))
65 |
66 | ## 0.5.0
67 |
68 | - Use PRQL logo icon for `.prql` file extensions and display in file explorer
69 | and editor title bar ([#39](https://github.com/PRQL/prql-vscode/issues/39))
70 | - Add PRQL to SQL context menus to PRQL editor title
71 | ([#41](https://github.com/PRQL/prql-vscode/issues/41))
72 | - Add Generate SQL File command
73 | ([#42](https://github.com/PRQL/prql-vscode/issues/42))
74 | - Rename PRQL - SQL Output panel to SQL Preview
75 | ([#46](https://github.com/PRQL/prql-vscode/issues/46))
76 | - Add prql.target setting and use it to compile PRQL to SQL
77 | ([#48](https://github.com/PRQL/prql-vscode/issues/48))
78 | - Provide Copy to Clipboard feature in Sql Preview
79 | ([#55](https://github.com/PRQL/prql-vscode/issues/55))
80 | - Update prql-js to v0.5.0 and use new CompileOptions for the target
81 | ([#65](https://github.com/PRQL/prql-vscode/issues/65))
82 | - Update CHANGELOG.md for v0.5.0 release
83 | ([#68](https://github.com/PRQL/prql-vscode/issues/68))
84 | - Add PRQL extension to Data Science and Formatters categories
85 | ([#70](https://github.com/PRQL/prql-vscode/issues/70))
86 | - Create and use new docs/images folder for extension features images in docs
87 | ([#72](https://github.com/PRQL/prql-vscode/issues/72))
88 | - Update README.md with new features and settings in 0.5.0 version release
89 | ([#76](https://github.com/PRQL/prql-vscode/issues/76))
90 |
91 | ## 0.4.2
92 |
93 | - Update [`prql-js`](https://github.com/PRQL/prql/tree/main/prql-js) compiler to
94 | version [0.4.2](https://github.com/PRQL/prql/releases/tag/0.4.2)
95 |
96 | ## 0.4.0
97 |
98 | - Upgrade the underlying compiler to
99 | [PRQL 0.4.0](https://github.com/PRQL/prql/releases/tag/0.4.0)
100 | - Detect PRQL based on language ID rather than file extension by
101 | [@jiripospisil](https://github.com/jiripospisil)
102 | ([#25](https://github.com/PRQL/prql-vscode/pull/25))
103 | - Upgrade prql-js to 0.4.0 by [@aljazerzen](https://github.com/aljazerzen)
104 | ([#29](https://github.com/PRQL/prql-vscode/pull/29))
105 | - Release on releases rather than tags by
106 | [@max-sixty](https://github.com/max-sixty)
107 | ([#23](https://github.com/PRQL/prql-vscode/pull/23))
108 |
109 | ## 0.3.4
110 |
111 | - Bump `prql-js` to 0.3.0
112 |
113 | ## 0.3.3
114 |
115 | - Bump `prql-js` to 0.2.11
116 |
117 | ## 0.3.2
118 |
119 | - Provide PRQL diagnostics
120 | - Rename repo to `prql-vscode`, from `prql-code`
121 | - Add GitHub Action to test on each PR
122 | - Add GitHub Action to release on each tag
123 |
124 | ## 0.3.0
125 |
126 | - Live transpiling from PRQL to SQL in a side panel
127 |
128 | ## 0.2.0
129 |
130 | - Update grammar to PRQL version 0.2
131 |
132 | ## 0.1.0
133 |
134 | - Initial release
135 | - Add syntax highlighting for PRQL
136 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "prql-vscode",
3 | "displayName": "PRQL",
4 | "description": "PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement",
5 | "repository": {
6 | "url": "https://github.com/prql/prql-vscode.git"
7 | },
8 | "publisher": "prql-lang",
9 | "version": "0.13.0",
10 | "icon": "resources/prql-logo.png",
11 | "engines": {
12 | "vscode": "^1.65.0"
13 | },
14 | "categories": [
15 | "Data Science",
16 | "Formatters",
17 | "Programming Languages"
18 | ],
19 | "keywords": [
20 | "data tools",
21 | "sql tools"
22 | ],
23 | "source": "src/extension.ts",
24 | "main": "out/extension.js",
25 | "activationEvents": [
26 | "onLanguage:prql",
27 | "onWebviewPanel:prql.sqlPreviewPanel",
28 | "onCommand:prql.openSqlPreview",
29 | "onCommand:prql.generateSqlFile",
30 | "onCommand:prql.copySqlToClipboard",
31 | "onCommand:prql.viewSettings"
32 | ],
33 | "contributes": {
34 | "languages": [
35 | {
36 | "id": "prql",
37 | "aliases": [
38 | "PRQL",
39 | "prql"
40 | ],
41 | "extensions": [
42 | ".prql"
43 | ],
44 | "configuration": "./language-configuration.json",
45 | "icon": {
46 | "dark": "./resources/prql-logo.png",
47 | "light": "./resources/prql-logo.png"
48 | }
49 | }
50 | ],
51 | "grammars": [
52 | {
53 | "language": "prql",
54 | "scopeName": "source.prql",
55 | "path": "./syntaxes/prql.tmLanguage.json"
56 | },
57 | {
58 | "injectTo": [
59 | "source.js",
60 | "source.jsx",
61 | "source.ts",
62 | "source.tsx"
63 | ],
64 | "scopeName": "inline.prql",
65 | "path": "./syntaxes/inline-prql.json",
66 | "embeddedLanguages": {
67 | "meta.embedded.block.prql": "prql"
68 | }
69 | }
70 | ],
71 | "commands": [
72 | {
73 | "command": "prql.openSqlPreview",
74 | "title": "Open SQL Preview",
75 | "category": "PRQL",
76 | "icon": "$(open-preview)"
77 | },
78 | {
79 | "command": "prql.generateSqlFile",
80 | "title": "Generate SQL File",
81 | "category": "PRQL",
82 | "icon": "$(database)"
83 | },
84 | {
85 | "command": "prql.copySqlToClipboard",
86 | "title": "Copy SQL to Clipboard",
87 | "category": "PRQL",
88 | "icon": "$(copy)"
89 | },
90 | {
91 | "command": "prql.viewSettings",
92 | "title": "View PRQL Settings",
93 | "category": "PRQL",
94 | "icon": "$(gear)"
95 | }
96 | ],
97 | "menus": {
98 | "explorer/context": [
99 | {
100 | "command": "prql.openSqlPreview",
101 | "when": "resourceLangId == prql",
102 | "group": "prql"
103 | },
104 | {
105 | "command": "prql.generateSqlFile",
106 | "when": "resourceLangId == prql",
107 | "group": "prql"
108 | }
109 | ],
110 | "editor/title": [
111 | {
112 | "command": "prql.openSqlPreview",
113 | "when": "resourceLangId == prql",
114 | "group": "navigation"
115 | },
116 | {
117 | "command": "prql.generateSqlFile",
118 | "when": "resourceLangId == prql",
119 | "group": "navigation"
120 | },
121 | {
122 | "command": "prql.viewSettings",
123 | "when": "resourceLangId == prql",
124 | "group": "navigation"
125 | },
126 | {
127 | "command": "prql.copySqlToClipboard",
128 | "when": "prql.sqlPreviewActive || resourceLangId == prql",
129 | "group": "navigation"
130 | }
131 | ],
132 | "editor/title/context": [
133 | {
134 | "command": "prql.openSqlPreview",
135 | "when": "resourceLangId == prql",
136 | "group": "prql"
137 | },
138 | {
139 | "command": "prql.generateSqlFile",
140 | "when": "resourceLangId == prql",
141 | "group": "prql"
142 | },
143 | {
144 | "command": "prql.viewSettings",
145 | "when": "resourceLangId == prql",
146 | "group": "prql"
147 | },
148 | {
149 | "command": "prql.copySqlToClipboard",
150 | "when": "prql.sqlPreviewActive || resourceLangId == prql",
151 | "group": "prql"
152 | }
153 | ]
154 | },
155 | "configuration": {
156 | "title": "PRQL",
157 | "type": "object",
158 | "properties": {
159 | "prql.target": {
160 | "type": "string",
161 | "enum": [
162 | "Ansi",
163 | "BigQuery",
164 | "ClickHouse",
165 | "DuckDb",
166 | "Generic",
167 | "MsSql",
168 | "MySql",
169 | "Postgres",
170 | "SQLite",
171 | "Snowflake",
172 | "Any"
173 | ],
174 | "default": "Generic",
175 | "order": 0,
176 | "description": "PRQL compiler target dialect to use when generating SQL from pipeline definition files."
177 | },
178 | "prql.addCompilerSignatureComment": {
179 | "type": "boolean",
180 | "default": true,
181 | "order": 1,
182 | "description": "Add PRQL compiler signature comment with SQL target dialect and compiler version to generated SQL."
183 | },
184 | "prql.addTargetDialectToSqlFilenames": {
185 | "type": "boolean",
186 | "default": false,
187 | "order": 2,
188 | "description": "Add target dialect suffix to the generated SQL filenames."
189 | }
190 | }
191 | }
192 | },
193 | "scripts": {
194 | "vscode:prepublish": "npm run compile",
195 | "compile": "eslint --max-warnings 0 . && tsc -p ./",
196 | "watch": "tsc -w -p ./"
197 | },
198 | "devDependencies": {
199 | "@eslint/eslintrc": "^3.3.3",
200 | "@eslint/js": "^9.39.1",
201 | "@types/node": "^25.0.1",
202 | "@typescript-eslint/eslint-plugin": "^8.49.0",
203 | "@typescript-eslint/parser": "^8.49.0",
204 | "eslint": "^9.39.1",
205 | "typescript": "^5.9.3"
206 | },
207 | "dependencies": {
208 | "@types/vscode": "^1.107.0",
209 | "prqlc": "^0.13.7",
210 | "shiki": "^0.14.7"
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PRQL extension for Visual Studio Code
2 |
3 | [](http://opensource.org/licenses/Apache-2.0)
4 | [](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)
5 | [](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)
6 | [](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)
7 | [](https://marketplace.visualstudio.com/items?itemName=PRQL-lang.prql-vscode)
8 |
9 | PRQL is a modern language for transforming data — a simple, powerful, pipelined
10 | SQL replacement.
11 |
12 | This extension adds [PRQL](https://prql-lang.org/) support to VS Code.
13 |
14 | 
15 |
16 | ## Features
17 |
18 | - [PRQL](https://prql-lang.org/) language support and syntax highlighting
19 | - SQL Previews with Problems diagnostics and PRQL errors display updated on every keystroke as you type PRQL
20 | - Dedicated SQL Previews linked to open PRQL documents in VS Code editor
21 | - Restore open SQL Previews on VS Code reload
22 | - Copy SQL from an open SQL Preview to VS Code Clipboard
23 | - Generate SQL File PRQL editor context menu shortcut
24 | - View PRQL Settings editor context menu shortcut
25 | - PRQL compile target setting for the generated SQL dialect
26 | - Multi-target SQL generation and file naming options
27 | - Optional PRQL compiler signature comment append in generated SQL
28 |
29 | 
30 |
31 | ### Feature Contributions
32 |
33 | PRQL extension contributes the following Settings, Commands, Languages and Activation Events to the VS Code:
34 |
35 | 
36 |
37 | ## Configuration
38 |
39 | Modify
40 | [User or Workspace Settings](https://code.visualstudio.com/docs/getstarted/settings#_creating-user-and-workspace-settings)
41 | in VS Code to change the default PRQL extension Settings globally or only for the open project workspace.
42 |
43 | 
44 |
45 | You can use new `View PRQL Settings` PRQL editor context menu shortcut to access and modify PRQL extension Settings:
46 |
47 | 
48 |
49 | ### PRQL Settings
50 |
51 | PRQL extension Settings allow you to customize PRQL [compiler options](https://github.com/PRQL/prql/tree/main/prqlc/bindings/js#usage) and filenames of the generated SQL files. Use the ⚙️ PRQL Settings shortcut from the open PRQL document editor context menu to access and change these configuration options.
52 |
53 | | Setting | Description |
54 | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55 | | `prql.target` | Specifies the default PRQL compiler target dialect to use when generating SQL from pipeline definition files (`.prql`) globally or in an open vscode project workspace. Defaults to `Generic`. |
56 | | `prql.addCompilerSignatureComment` | Adds `Generated by PRQL compiler version ...` signature comment with SQL target dialect info used to create SQL from PRQL document. Defaults to `true`. Set this setting to `false` to stop PRQL compiler from adding `Generated by ...` line to the end of the created SQL. |
57 | | `prql.addTargetDialectToSqlFilenames` | Adds target dialect suffix to the generated SQL filenames when `Generate Sql File` PRQL document command is used. Defaults to `false`. Set this setting to `true` when targeting multiple database systems with different SQL flavors. For example, projects using [`PostgreSQL`](https://www.postgresql.org/) transaction database and [`DuckDB`](https://duckdb.org/) OLAP database management system for analytics can use this option to generate different SQL from PRQL query documents. PRQL extension will save generated SQL documents as `*.postgre.sql` and `*.duckdb.sql` when using `Generate SQL File` command with the currently selected `prql.target` in PRQL Settings set to `Postgre` or `DuckDB`. |
58 |
59 | ### PRQL Target
60 |
61 | PRQL extension and the underlying [`prqlc-js`](https://github.com/PRQL/prql/tree/main/prqlc/bindings/js#usage) compiler used by this extension supports the following PRQL target dialect options: `Ansi`, `BigQuery`, `ClickHouse`, `DuckDb`, `Generic`, `MsSql`, `MySql`, `Postgres`, `SQLite`, `Snowflake`, and `Any`.
62 |
63 | The `prql.target` extension setting default option value is `Generic`, which will produce SQL that should work with most database management systems. We recommend you set it to the target DB you are working with in your project [workspace settings](https://code.visualstudio.com/docs/getstarted/settings#_creating-user-and-workspace-settings).
64 |
65 | You can also disable this PRQL compiler option in vscode extension by setting `prql.target` to `Any`. When `prql.target` is set to `Any`, PRQL compiler will read the target SQL dialect from `.prql` file header as described in [PRQL Language Book](https://prql-lang.org/book/project/target.html). For example, setting `prql.target` to `Any` and adding `prql target:sql.postgres` on the first line of your `.prql` query file will produce SQL for `PostgreSQL` database. Otherwise, `Generic` SQL flavor will be used for the generated SQL.
66 |
67 | ## Deploying the Extension
68 |
69 | This repo has the machinery to update the VSCode extension to the Microsoft Marketplace.
70 |
71 | When there is a new version of `prqlc` in `npm`, dependabot will PR an update.
72 | Once per day, the _.github/dependabot.yml_ file checks NPM and compares the `dependencies.prqlc` property in _package.json_ to the latest version in NPM. If they differ, dependabot creates a PR for _package.json_.
73 |
74 | Once that has been merged, the following manual steps will publish an update for the extension:
75 |
76 | - Update the [_CHANGELOG.md_](CHANGELOG.md) file, as needed
77 |
78 | - In _package.json_, update the `version` to match. This sets the version number of the extension itself.
79 |
80 | - Run `npm install` to update the `package-lock.json`
81 |
82 | - Create a new release from Github. This will start a workflow to release the current version to the VS Code Marketplace.
83 |
84 | - NB: From time to time, check the `node-version` in the files*.github/workflows/pull-request.yaml* and _.github/workflows/release.yml_. We track Node.js LTS - version 20 in June 2024.
85 |
86 | ## Developing the Extension
87 |
88 | - Clone the repository and install dependencies:
89 |
90 | ```sh
91 | git clone git@github.com:prql/prql-vscode.git
92 | cd prql-vscode && npm install
93 | ```
94 |
95 | - Open the project in VS Code and start the TypeScript compilation task via
96 | `Command Palette` -> `Tasks: Run build task` -> `npm: watch`. Alternatively,
97 | you can run the compilation in your terminal directly:
98 |
99 | ```sh
100 | npm run watch
101 | ```
102 |
103 | - Launch the extension in the Run and Debug panel. If you need to develop
104 | against a local version of `prql-js`, use `npm link` and restart the
105 | compilation task:
106 |
107 | ```sh
108 | npm link ../prql/prql-js
109 | ```
110 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/src/views/sqlPreview.ts:
--------------------------------------------------------------------------------
1 | import {
2 | commands,
3 | window,
4 | workspace,
5 | Disposable,
6 | Event,
7 | ExtensionContext,
8 | TextDocument,
9 | TextDocumentChangeEvent,
10 | ViewColumn,
11 | Webview,
12 | WebviewPanel,
13 | WebviewPanelOnDidChangeViewStateEvent,
14 | Uri,
15 | } from 'vscode';
16 |
17 | import * as shiki from 'shiki';
18 |
19 | import { readFileSync } from 'node:fs';
20 | import * as fs from 'fs';
21 | import * as path from 'path';
22 |
23 | import { ViewContext } from './viewContext';
24 | import { CompilationResult } from './compilationResult';
25 |
26 | import { compile } from '../compiler';
27 | import * as constants from '../constants';
28 |
29 | /**
30 | * Defines Sql Preview class for managing state and behaviour of Sql Preview webview panel(s).
31 | */
32 | export class SqlPreview {
33 | // view tracking vars
34 | public static currentView: SqlPreview | undefined;
35 | private static _views: Map = new Map<
36 | string,
37 | SqlPreview
38 | >();
39 |
40 | // view instance vars
41 | private readonly _webviewPanel: WebviewPanel;
42 | private readonly _documentUri: Uri;
43 | private readonly _viewUri: Uri;
44 |
45 | private _disposables: Disposable[] = [];
46 | private _highlighter: shiki.Highlighter | undefined;
47 | private _lastCompilationResult: CompilationResult | undefined;
48 |
49 | /**
50 | * Reveals current Sql Preview webview
51 | * or creates new Sql Preview webview panel
52 | * for the given PRQL document Uri
53 | * from an open and active PRQL document editor.
54 | *
55 | * @param context Extension context.
56 | * @param documentUri PRQL document Uri.
57 | * @param webviewPanel Optional webview panel instance.
58 | * @param viewConfig View config to restore.
59 | */
60 | public static render(
61 | context: ExtensionContext,
62 | documentUri: Uri,
63 | webviewPanel?: WebviewPanel,
64 | ) {
65 | // attempt to reveal an open sql preview
66 | const sqlPreview: SqlPreview | undefined = SqlPreview.reveal(
67 | context,
68 | documentUri,
69 | );
70 |
71 | if (sqlPreview === undefined) {
72 | if (!webviewPanel) {
73 | // create new webview panel for the prql document sql preview
74 | webviewPanel = SqlPreview.createWebviewPanel(context, documentUri);
75 | } else {
76 | // enable scripts for existing webview panel
77 | webviewPanel.webview.options = {
78 | enableScripts: true,
79 | enableCommandUris: true,
80 | };
81 | }
82 |
83 | // create new sql peview and set it as current view
84 | SqlPreview.currentView = new SqlPreview(
85 | context,
86 | webviewPanel,
87 | documentUri,
88 | );
89 | }
90 |
91 | sqlPreview?.updateActiveSqlPreviewContext(context);
92 | }
93 |
94 | /**
95 | * Reveals an open Sql Preview for a given PRQL document URI.
96 | *
97 | * @param context Extension context.
98 | * @param documentUri PRQL document Uri.
99 | * @returns The corresponding open Sql Preview for a PRQL document URI, or undefined.
100 | */
101 | public static reveal(
102 | context: ExtensionContext,
103 | documentUri: Uri,
104 | ): SqlPreview | undefined {
105 | // create view Uri
106 | const viewUri: Uri = documentUri.with({ scheme: 'prql' });
107 |
108 | // get an open sql preview
109 | const sqlPreview: SqlPreview | undefined = SqlPreview._views.get(
110 | viewUri.toString(true),
111 | ); // skip encoding
112 |
113 | if (sqlPreview !== undefined) {
114 | // show loaded webview panel
115 | sqlPreview.reveal(context);
116 | SqlPreview.currentView = sqlPreview;
117 | return sqlPreview;
118 | } else {
119 | SqlPreview.clearActiveSqlPreviewContext(context);
120 | }
121 |
122 | return undefined;
123 | }
124 |
125 | /**
126 | * clears active sql preview, context values and prql.sql in workspace state.
127 | *
128 | * @param context Extension context.
129 | */
130 | public static clearActiveSqlPreviewContext(context: ExtensionContext) {
131 | SqlPreview.currentView = undefined;
132 | commands.executeCommand('setContext', ViewContext.SqlPreviewActive, false);
133 | commands.executeCommand(
134 | 'setContext',
135 | ViewContext.LastActivePrqlDocumentUri,
136 | undefined,
137 | );
138 | context.workspaceState.update('prql.sql', undefined);
139 | }
140 |
141 | /**
142 | * Creates new webview panel for the given prql source document Uri.
143 | *
144 | * @param context Extension context.
145 | * @param documentUri PRQL source document Uri.
146 | * @returns New webview panel instance.
147 | */
148 | private static createWebviewPanel(
149 | context: ExtensionContext,
150 | documentUri: Uri,
151 | ): WebviewPanel {
152 | // create sql preview filename for the webview panel title display
153 | const fileName = path.basename(documentUri.path, '.prql'); // strip out prql file ext.
154 |
155 | // create new sql preview webview panel
156 | const webviewPanel = window.createWebviewPanel(
157 | constants.SqlPreviewPanel, // webview panel view type
158 | `${fileName}.sql`, // webview panel title
159 | {
160 | viewColumn: ViewColumn.Beside, // display it on the side
161 | preserveFocus: true,
162 | },
163 | {
164 | // webview panel options
165 | enableScripts: true, // enable JavaScript in webview
166 | enableCommandUris: true,
167 | enableFindWidget: true,
168 | retainContextWhenHidden: true,
169 | localResourceRoots: [Uri.joinPath(context.extensionUri, 'resources')],
170 | },
171 | );
172 |
173 | // set custom sql preview panel icon
174 | webviewPanel.iconPath = Uri.file(
175 | path.join(context.extensionUri.fsPath, 'resources', 'prql-logo.png'),
176 | );
177 |
178 | return webviewPanel;
179 | }
180 |
181 | /**
182 | * Creates new SqlPreview webview panel instance.
183 | *
184 | * @param context Extension context.
185 | * @param webviewPanel Reference to the webview panel.
186 | * @param documentUri PRQL document Uri.
187 | */
188 | private constructor(
189 | context: ExtensionContext,
190 | webviewPanel: WebviewPanel,
191 | documentUri: Uri,
192 | ) {
193 | // save view context info
194 | this._webviewPanel = webviewPanel;
195 | this._documentUri = documentUri;
196 | this._viewUri = documentUri.with({ scheme: 'prql' });
197 |
198 | // configure webview panel
199 | this.configure(context);
200 |
201 | // add it to the tracked sql preview webviews
202 | SqlPreview._views.set(this._viewUri.toString(true), this);
203 |
204 | // update view context values on webview state change
205 | this._webviewPanel.onDidChangeViewState(
206 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
207 | (viewChangeEvent: WebviewPanelOnDidChangeViewStateEvent) => {
208 | if (this._webviewPanel.active) {
209 | // update view context values
210 | commands.executeCommand(
211 | 'setContext',
212 | ViewContext.SqlPreviewActive,
213 | true,
214 | );
215 | commands.executeCommand(
216 | 'setContext',
217 | ViewContext.LastActivePrqlDocumentUri,
218 | documentUri,
219 | );
220 | SqlPreview.currentView = this;
221 | } else {
222 | // clear sql preview context
223 | commands.executeCommand(
224 | 'setContext',
225 | ViewContext.SqlPreviewActive,
226 | false,
227 | );
228 | SqlPreview.currentView = undefined;
229 | }
230 | },
231 | );
232 |
233 | // add prql text document change handler
234 | [
235 | workspace.onDidOpenTextDocument,
236 | workspace.onDidChangeTextDocument,
237 | ].forEach((event: Event | Event) => {
238 | this._disposables.push(
239 | event(
240 | this.debounce(() => {
241 | this.update(context);
242 | }, 10),
243 | ),
244 | );
245 | });
246 |
247 | // add active text editor change handler
248 | this._disposables.push(
249 | window.onDidChangeActiveTextEditor((editor) => {
250 | if (editor && editor.document.uri.fsPath === this.documentUri.fsPath) {
251 | // clear sql preview context and recompile prql
252 | // from the linked and active PRQL editor
253 | // for the webview's PRQL source document
254 | this.clearSqlPreviewContext(context);
255 | this.update(context);
256 | }
257 | }),
258 | );
259 |
260 | // add color theme change handler
261 | this._disposables.push(
262 | window.onDidChangeActiveColorTheme(() => {
263 | // reset highlighter
264 | this._highlighter = undefined;
265 |
266 | // notify webview
267 | webviewPanel.webview.postMessage({ command: 'changeTheme' });
268 | }),
269 | );
270 |
271 | // add dispose resources handler
272 | this._webviewPanel.onDidDispose(() => this.dispose(context));
273 | }
274 |
275 | /**
276 | * Debounce for sql preview updates on prql text changes.
277 | *
278 | * @param fn
279 | * @param timeout
280 | * @returns
281 | */
282 | private debounce(fn: () => any, timeout: number) {
283 | let timer: NodeJS.Timeout | undefined;
284 | return () => {
285 | clearTimeout(timer);
286 | timer = setTimeout(() => {
287 | fn();
288 | }, timeout);
289 | };
290 | }
291 |
292 | /**
293 | * Disposes Sql Preview webview resources when webview panel is closed.
294 | */
295 | public dispose(context: ExtensionContext) {
296 | SqlPreview.currentView = undefined;
297 | SqlPreview._views.delete(this._viewUri.toString(true)); // skip encoding
298 | this._disposables.forEach((d) => d.dispose());
299 | this.clearSqlPreviewContext(context);
300 | }
301 |
302 | /**
303 | * Reveals loaded Sql Preview and sets it as active in vscode editor panel.
304 | *
305 | * @param context Extension context.
306 | */
307 | public reveal(context: ExtensionContext) {
308 | const viewColumn: ViewColumn = ViewColumn.Active
309 | ? ViewColumn.Active
310 | : ViewColumn.One;
311 | this.webviewPanel.reveal(viewColumn, true); // preserve current active editor focus
312 | this.updateActiveSqlPreviewContext(context);
313 | }
314 |
315 | /**
316 | * Configures webview html for Sql Preview display,
317 | * and registers webview message request handlers for updates.
318 | *
319 | * @param context Extension context.
320 | * @param viewConfig Sql Preview config.
321 | */
322 | private async configure(context: ExtensionContext) {
323 | // set view html content for the webview panel
324 | this.webviewPanel.webview.html = this.getHtmlTemplate(
325 | context,
326 | this.webviewPanel.webview,
327 | );
328 |
329 | // process webview messages
330 | this.webviewPanel.webview.onDidReceiveMessage(
331 | (message: any) => {
332 | const command: string = message.command;
333 | switch (command) {
334 | case 'refresh':
335 | // reload data view and config
336 | this.refresh();
337 | break;
338 | }
339 | },
340 | undefined,
341 | this._disposables,
342 | );
343 |
344 | let prqlCode = undefined;
345 | if (
346 | fs.existsSync(this.documentUri.fsPath) &&
347 | this.documentUri.fsPath.endsWith('.prql')
348 | ) {
349 | // load initial prql code from file
350 | const prqlContent: Uint8Array = await workspace.fs.readFile(
351 | Uri.file(this.documentUri.fsPath),
352 | );
353 |
354 | const textDecoder = new TextDecoder('utf8');
355 | prqlCode = textDecoder.decode(prqlContent);
356 | }
357 |
358 | // update webview
359 | this.refresh();
360 | this.update(context, prqlCode);
361 | }
362 |
363 | /**
364 | * Loads and creates html template for Sql Preview webview.
365 | *
366 | * @param context Extension context.
367 | * @param webview Sql Preview webview.
368 | * @returns Html template to use for Sql Preview webview.
369 | */
370 | private getHtmlTemplate(context: ExtensionContext, webview: Webview): string {
371 | // load webview html template, stylesheet and sql preview script
372 | const htmlTemplate = readFileSync(
373 | this.getResourceUri(context, 'sql-preview.html').fsPath,
374 | 'utf-8',
375 | );
376 | const stylesheetUri: Uri = this.getResourceUri(context, 'sql-preview.css');
377 | const scriptUri: Uri = this.getResourceUri(context, 'sqlPreview.js');
378 |
379 | // inject webview resource urls into the loaded webview html template,
380 | // add webview CSP source, and convert css and js Uris to webview resource Uris
381 | return htmlTemplate
382 | .replace(/##CSP_SOURCE##/g, webview.cspSource)
383 | .replace('##CSS_URI##', webview.asWebviewUri(stylesheetUri).toString())
384 | .replace('##JS_URI##', webview.asWebviewUri(scriptUri).toString());
385 | }
386 |
387 | /**
388 | * Gets webview resource Uri from extension directory.
389 | *
390 | * @param context Extension context.
391 | * @param filename Resource filename to create resource Uri.
392 | * @returns Webview resource Uri.
393 | */
394 | private getResourceUri(context: ExtensionContext, fileName: string) {
395 | return Uri.joinPath(context.extensionUri, 'resources', fileName);
396 | }
397 |
398 | /**
399 | * Reloads Sql Preview for the active PRQL document Uri or on vscode IDE reload.
400 | */
401 | public async refresh(): Promise {
402 | // update view state
403 | this.webviewPanel.webview.postMessage({
404 | command: 'refresh',
405 | documentUrl: this.documentUri.fsPath,
406 | });
407 | }
408 |
409 | /**
410 | * Updates Sql Preview with new PRQL compilation results
411 | * from the active PRQL text editor.
412 | *
413 | * @param context Extension context.
414 | * @param prqlCode Optional prql code overwrite to use
415 | * instead of text from the active vscode PRQL editor.
416 | */
417 | private update(context: ExtensionContext, prqlCode?: string) {
418 | // check active text editor
419 | const editor = window.activeTextEditor;
420 | if (
421 | this.webviewPanel.visible &&
422 | editor &&
423 | editor.document.languageId === 'prql' &&
424 | editor.document.uri.fsPath === this.documentUri.fsPath
425 | ) {
426 | // get updated prql code from the active PRQL text editor
427 | const prqlCode = editor.document.getText();
428 | this.processPrql(context, prqlCode);
429 | } else if (prqlCode) {
430 | this.processPrql(context, prqlCode);
431 | }
432 | }
433 |
434 | /**
435 | * Updates current/active SQL Preview context and view state.
436 | *
437 | * @param context Extension context.
438 | */
439 | private async updateActiveSqlPreviewContext(context: ExtensionContext) {
440 | commands.executeCommand('setContext', ViewContext.SqlPreviewActive, true);
441 | commands.executeCommand(
442 | 'setContext',
443 | ViewContext.LastActivePrqlDocumentUri,
444 | this.documentUri,
445 | );
446 | // update active sql preview sql text in workspace state
447 | context.workspaceState.update('prql.sql', this._lastCompilationResult?.sql);
448 | }
449 |
450 | /**
451 | * Clears SQL Preview context and view state.
452 | *
453 | * @param context Extension context.
454 | */
455 | private async clearSqlPreviewContext(context: ExtensionContext) {
456 | commands.executeCommand('setContext', ViewContext.SqlPreviewActive, false);
457 | context.workspaceState.update('prql.sql', undefined);
458 | }
459 |
460 | /**
461 | * Processes given prql code.
462 | *
463 | * @param context Extension context.
464 | * @param prqlCode PRQL code to process.
465 | */
466 | private async processPrql(context: ExtensionContext, prqlCode: string) {
467 | this.compilePrql(prqlCode, this._lastCompilationResult?.lastSqlHtml).then(
468 | (compilationResult) => {
469 | if (compilationResult.status === 'ok') {
470 | // save the last valid compilation result
471 | // to show it when errors occur later,
472 | // or on sql preview panel reveal
473 | this._lastCompilationResult = compilationResult;
474 | }
475 |
476 | // update webview
477 | this.webviewPanel.webview.postMessage({
478 | command: 'update',
479 | result: compilationResult,
480 | });
481 |
482 | this.updateActiveSqlPreviewContext(context);
483 | },
484 | );
485 | }
486 |
487 | /**
488 | * Compiles prql code and returns generated sql,
489 | * and formatted html sql compilation result.
490 | *
491 | * @param prqlCode PRQL code to compile.
492 | * @param lastSqlHtml Last valid sql html output.
493 | * @returns Compilation result in sql and html formats.
494 | */
495 | private async compilePrql(
496 | prqlCode: string,
497 | lastSqlHtml: string | undefined,
498 | ): Promise {
499 | // compile given prql code
500 | const sqlCode = compile(prqlCode);
501 | if (Array.isArray(sqlCode)) {
502 | // return last valid sql html output with new error info
503 | return {
504 | status: 'error',
505 | error: {
506 | message: sqlCode[0].display ?? sqlCode[0].reason,
507 | },
508 | lastSqlHtml: lastSqlHtml,
509 | };
510 | }
511 |
512 | // create html to display for the generated sql
513 | const highlighter = await this.getHighlighter();
514 | const sqlHtml = highlighter.codeToHtml(sqlCode, { lang: 'sql' });
515 |
516 | return { status: 'ok', sqlHtml: sqlHtml, sql: sqlCode };
517 | }
518 |
519 | /**
520 | * Gets shiki code highlighter instance to create html formatted sql output.
521 | *
522 | * @returns Shiki highlighter instance with UI theme matching vscode color theme.
523 | */
524 | private async getHighlighter(): Promise {
525 | if (this._highlighter) {
526 | return Promise.resolve(this._highlighter);
527 | }
528 | return (this._highlighter = await shiki.getHighlighter({
529 | theme: this.themeName,
530 | }));
531 | }
532 |
533 | /**
534 | * Gets shiki highlighter theme name that matches current vscode color theme
535 | * to use it as the UI theme for this Sql Preview webview.
536 | */
537 | get themeName(): string {
538 | // get current vscode color UI theme name
539 | let colorTheme = workspace
540 | .getConfiguration('workbench')
541 | .get('colorTheme', 'dark-plus'); // default to dark plus
542 |
543 | if (shiki.BUNDLED_THEMES.includes(colorTheme as shiki.Theme)) {
544 | return colorTheme;
545 | }
546 |
547 | // try normalized color theme name
548 | colorTheme = colorTheme
549 | .toLowerCase()
550 | .replace('theme', '')
551 | .replace(/\s+/g, '-');
552 | if (shiki.BUNDLED_THEMES.includes(colorTheme as shiki.Theme)) {
553 | return colorTheme;
554 | }
555 |
556 | // ??? not sure what this means, or does.
557 | // Does it use the loaded vscode CSS vars
558 | // when no color theme is set?
559 | return 'css-variables';
560 | }
561 |
562 | /**
563 | * Gets the last valid compilation result for this sql preview.
564 | */
565 | get lastCompilationResult(): CompilationResult | undefined {
566 | return this._lastCompilationResult;
567 | }
568 |
569 | /**
570 | * Gets the underlying webview panel instance for this view.
571 | */
572 | get webviewPanel(): WebviewPanel {
573 | return this._webviewPanel;
574 | }
575 |
576 | /**
577 | * Gets view panel visibility status.
578 | */
579 | get visible(): boolean {
580 | return this._webviewPanel.visible;
581 | }
582 |
583 | /**
584 | * Gets the source document uri for this view.
585 | */
586 | get documentUri(): Uri {
587 | return this._documentUri;
588 | }
589 |
590 | /**
591 | * Gets the view uri to load on sql preview command triggers or vscode IDE reload.
592 | */
593 | get viewUri(): Uri {
594 | return this._viewUri;
595 | }
596 | }
597 |
--------------------------------------------------------------------------------