From 0c90d49efdec59807ca5d832dbba88fd85a299d6 Mon Sep 17 00:00:00 2001 From: Robert Ostermann Date: Wed, 26 Jul 2023 22:32:55 -0500 Subject: [PATCH] v2.3.9 --- CHANGELOG.md | 6 ++- package.json | 52 +++++++++++++++++-- src/extension.ts | 51 +++++++++++++----- src/features/helper/configuration.ts | 31 ++++++++++- .../helper/types/formatLanguageSettings.ts | 5 ++ src/features/providers/formatter/helper.ts | 24 ++++++++- .../providers/formatter/rangeFormat.ts | 31 +++++++++-- test/.vscode/settings.json | 12 ++--- test/suite/test_sql/markdown.md | 10 ++-- 9 files changed, 186 insertions(+), 36 deletions(-) create mode 100644 src/features/helper/types/formatLanguageSettings.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b99c61c..819b470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ All notable changes to the "sqlfluff" extension will be documented in this file. -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. +## [2.3.9] - 2023-07-26 + +- Update the `sqlfluff.format.languages` to accept objects containing formatting settings for the languages to address this [Feature Request](https://github.com/sqlfluff/vscode-sqlfluff/issues/94) +- Format selection now can format while preserving the whitespace (based on first line) +- `SQLFluff Format Selection` now can be placed in the context menu. ## [2.3.8] - 2023-07-16 diff --git a/package.json b/package.json index 660fa11..8ddf76d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-sqlfluff", "displayName": "sqlfluff", - "version": "2.3.8", + "version": "2.3.9", "description": "A linter and auto-formatter for SQLfluff, a popular linting tool for SQL and dbt.", "publisher": "dorzey", "icon": "images/icon.png", @@ -38,17 +38,22 @@ { "command": "sqlfluff.debug", "title": "SQLFluff Debug Extension", - "when": "extensionLangId == sql || extensionLangId == sql-bigquery || extensionLangId == jinja-sql || extensionLangId == postgres || extensionLangId == snowflake-sql" + "enablement": "editorLangId in sqlfluff.formatLanguages" }, { "command": "sqlfluff.fix", "title": "SQLFluff Fix", - "when": "extensionLangId == sql || extensionLangId == sql-bigquery || extensionLangId == jinja-sql || extensionLangId == postgres || extensionLangId == snowflake-sql" + "enablement": "editorLangId in sqlfluff.formatLanguages" + }, + { + "command": "sqlfluff.format.selection", + "title": "SQLFluff Format Selection", + "enablement": "editorLangId in sqlfluff.formatLanguages && editorHasSelection" }, { "command": "sqlfluff.lint", "title": "SQLFluff Lint", - "when": "extensionLangId == sql || extensionLangId == sql-bigquery || extensionLangId == jinja-sql || extensionLangId == postgres || extensionLangId == snowflake-sql" + "enablement": "editorLangId in sqlfluff.formatLanguages" }, { "command": "sqlfluff.lintProject", @@ -57,9 +62,18 @@ { "command": "sqlfluff.showOutputChannel", "title": "SQLFluff Show Output Channel", - "when": "extensionLangId == sql || extensionLangId == sql-bigquery || extensionLangId == jinja-sql || extensionLangId == postgres || extensionLangId == snowflake-sql" + "enablement": "editorLangId in sqlfluff.formatLanguages" } ], + "menus": { + "editor/context": [ + { + "command": "sqlfluff.format.selection", + "group": "1_modification@10", + "when": "editorLangId in sqlfluff.formatLanguages && editorHasSelection" + } + ] + }, "configuration": { "properties": { "sqlfluff.config": { @@ -262,6 +276,34 @@ "postgres", "snowflake-sql" ], + "items": { + "type": [ + "string", + "object" + ], + "additionalProperties": false, + "required": [ + "language", + "contextMenuFormatOptions", + "preserveLeadingWhitespace" + ], + "properties": { + "language": { + "type": "string", + "markdownDescription": "The language to enable formatting for." + }, + "contextMenuFormatOptions": { + "type": "boolean", + "default": false, + "markdownDescription": "Show a SQLFluff option in the context menu." + }, + "preserveLeadingWhitespace": { + "type": "boolean", + "default": false, + "markdownDescription": "Only works with `Format on Selection`. Preserve whitespace based on the first line of the selection." + } + } + }, "description": "The languages formatting is enabled for." }, "sqlfluff.experimental.format.executeInTerminal": { diff --git a/src/extension.ts b/src/extension.ts index 157bb14..df011ae 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,6 +7,7 @@ import Configuration from "./features/helper/configuration"; import Utilities from "./features/helper/utilities"; import LinterProvider from "./features/linter"; import Debug from "./features/providers/debug"; +import { FormatSelectionProvider } from "./features/providers/formatter/rangeFormat"; import HoverProvider from "./features/providers/linter/actions/hover"; import QuickFixProvider from "./features/providers/linter/actions/quickFix"; @@ -51,6 +52,8 @@ export const activate = (context: vscode.ExtensionContext) => { context.subscriptions.push(hoverProvider); }); + vscode.commands.executeCommand("setContext", "sqlfluff.formatLanguages", formatSelectors) + context.subscriptions.push(vscode.commands.registerCommand(EXCLUDE_RULE, ExcludeRules.toggleRule)); context.subscriptions.push(vscode.commands.registerCommand(EXCLUDE_RULE_WORKSPACE, ExcludeRules.toggleRuleWorkspace)); context.subscriptions.push(vscode.commands.registerCommand(VIEW_DOCUMENTATION, Documentation.showDocumentation)); @@ -58,47 +61,71 @@ export const activate = (context: vscode.ExtensionContext) => { const lintCommand = "sqlfluff.lint"; const lintCommandHandler = () => { if (vscode.window.activeTextEditor) { - const currentDocument = vscode.window.activeTextEditor.document; - if (currentDocument) { - lintingProvider.doLint(currentDocument, true); + const document = vscode.window.activeTextEditor.document; + if (document) { + lintingProvider.doLint(document, true); } } }; + context.subscriptions.push(vscode.commands.registerCommand(lintCommand, lintCommandHandler)); const lintProjectCommand = "sqlfluff.lintProject"; const lintProjectCommandHandler = () => { if (vscode.window.activeTextEditor) { - const currentDocument = vscode.window.activeTextEditor.document; - if (currentDocument) { + const document = vscode.window.activeTextEditor.document; + if (document) { lintingProvider.lintProject(true); } } }; + context.subscriptions.push(vscode.commands.registerCommand(lintProjectCommand, lintProjectCommandHandler)); const fixCommand = "sqlfluff.fix"; const fixCommandHandler = () => { if (vscode.window.activeTextEditor) { - const currentDocument = vscode.window.activeTextEditor.document; - if (currentDocument) { + const document = vscode.window.activeTextEditor.document; + if (document) { vscode.commands.executeCommand("editor.action.formatDocument"); } } }; + context.subscriptions.push(vscode.commands.registerCommand(fixCommand, fixCommandHandler)); + + const formatSelection = "sqlfluff.format.selection"; + const formatSelectionHandler = async () => { + if (vscode.window.activeTextEditor) { + // Check if available language + const document = vscode.window.activeTextEditor.document; + const range = new vscode.Range( + vscode.window.activeTextEditor.selection.start, + vscode.window.activeTextEditor.selection.end, + ) + + const textEdits = await FormatSelectionProvider.provideTextEdits( + document, + range, + ); + + textEdits.forEach((textEdit) => { + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.replace(document.uri, textEdit.range, textEdit.newText); + + vscode.workspace.applyEdit(workspaceEdit); + }) + } + }; + context.subscriptions.push(vscode.commands.registerCommand(formatSelection, formatSelectionHandler)); const debugCommand = "sqlfluff.debug"; const debugCommandHandler = async () => { Debug.debug(); }; + context.subscriptions.push(vscode.commands.registerCommand(debugCommand, debugCommandHandler)); const showOutputChannelCommand = "sqlfluff.showOutputChannel"; const showOutputChannelCommandHandler = async () => { Utilities.outputChannel.show(); }; - - context.subscriptions.push(vscode.commands.registerCommand(lintCommand, lintCommandHandler)); - context.subscriptions.push(vscode.commands.registerCommand(lintProjectCommand, lintProjectCommandHandler)); - context.subscriptions.push(vscode.commands.registerCommand(fixCommand, fixCommandHandler)); - context.subscriptions.push(vscode.commands.registerCommand(debugCommand, debugCommandHandler)); context.subscriptions.push(vscode.commands.registerCommand(showOutputChannelCommand, showOutputChannelCommandHandler)); }; diff --git a/src/features/helper/configuration.ts b/src/features/helper/configuration.ts index 35cfbd4..caa3325 100644 --- a/src/features/helper/configuration.ts +++ b/src/features/helper/configuration.ts @@ -5,6 +5,7 @@ import { DiagnosticSeverity } from "vscode"; import DiagnosticSetting from "./types/diagnosticSetting"; import EnvironmentVariable from "./types/environmentVariable"; +import FormatLanguageSettings from "./types/formatLanguageSettings"; import RunTrigger from "./types/runTrigger"; import Variables from "./types/variables"; import Utilities from "./utilities"; @@ -233,13 +234,41 @@ export default class Configuration { } public static formatLanguages(): string[] { - const languages: any = vscode.workspace + const languageSettings: (FormatLanguageSettings | string)[] | undefined = vscode.workspace .getConfiguration("sqlfluff.format") .get("languages"); + + const languages: string[] = []; + languageSettings?.forEach((languageSetting: FormatLanguageSettings | string) => { + if (typeof languageSetting === "string") { + languages.push(languageSetting); + } else { + languages.push(languageSetting.language) + } + }) + return languages; } + public static formatLanguageSetting(languageId: string): FormatLanguageSettings | undefined { + const languageSettings: (FormatLanguageSettings | string)[] | undefined = vscode.workspace + .getConfiguration("sqlfluff.format") + .get("languages"); + + const setting = languageSettings?.find((languageSetting: FormatLanguageSettings | string) => { + if (typeof languageSettings === "string") return false; + + const typedSetting = languageSetting as unknown as FormatLanguageSettings; + if (typedSetting.language === languageId) return true; + + return false; + }); + + if (typeof setting === "string") return undefined + return setting; + } + public static executeInTerminal(): boolean { return vscode.workspace .getConfiguration("sqlfluff.experimental.format") diff --git a/src/features/helper/types/formatLanguageSettings.ts b/src/features/helper/types/formatLanguageSettings.ts new file mode 100644 index 0000000..dd5bf35 --- /dev/null +++ b/src/features/helper/types/formatLanguageSettings.ts @@ -0,0 +1,5 @@ +export default interface FormatLanguageSettings { + language: string; + contextMenuFormatOptions: boolean; + preserveLeadingWhitespace: boolean; +} diff --git a/src/features/providers/formatter/helper.ts b/src/features/providers/formatter/helper.ts index ccfc664..548edcf 100644 --- a/src/features/providers/formatter/helper.ts +++ b/src/features/providers/formatter/helper.ts @@ -29,6 +29,28 @@ export default class FormatHelper { return undefined; } - return lines; + const parsedLines = lines[0].split(/\r?\n|\r|\n/g); + return parsedLines; + } + + public static addLeadingWhitespace(lines: string[], languageId: string, leadingWhitespace: number): string[] | undefined { + const formatSettings = Configuration.formatLanguageSetting(languageId) + let linesWithWhitespace: string[] = []; + + if (formatSettings?.preserveLeadingWhitespace) { + lines.forEach((line) => { + const emptySpace = new Array(leadingWhitespace).join(" "); + const whitespaceLine = line === "" ? line : emptySpace.concat(line); + linesWithWhitespace.push(whitespaceLine); + }) + } else { + linesWithWhitespace = lines; + } + + if (linesWithWhitespace.length > 0 && linesWithWhitespace.slice(-1)[0] === "") { + linesWithWhitespace.pop(); + } + + return linesWithWhitespace; } } diff --git a/src/features/providers/formatter/rangeFormat.ts b/src/features/providers/formatter/rangeFormat.ts index b535999..2d950ba 100644 --- a/src/features/providers/formatter/rangeFormat.ts +++ b/src/features/providers/formatter/rangeFormat.ts @@ -14,7 +14,17 @@ export class RangeFormattingProvider implements vscode.DocumentRangeFormattingEd range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken, + ): Promise { + const textEdits = await FormatSelectionProvider.provideTextEdits(document, range); + + return textEdits; + } +} +export class FormatSelectionProvider { + static async provideTextEdits( + document: vscode.TextDocument, + range: vscode.Range, ): Promise { const filePath = Utilities.normalizePath(document.fileName); const workspaceFolder = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined; @@ -22,12 +32,18 @@ export class RangeFormattingProvider implements vscode.DocumentRangeFormattingEd const workingDirectory = Configuration.workingDirectory(rootPath); const textEdits: vscode.TextEdit[] = []; + const endCharacter = document.lineAt(range.end.line).range.end.character; + const lineRange = new vscode.Range( + new vscode.Position(range.start.line, 0), + new vscode.Position(range.end.line, endCharacter), + ); + if (workingDirectory?.includes("${")) { return []; } Utilities.appendHyphenatedLine(); - Utilities.outputChannel.appendLine(`Range (Lines ${range.start.line} to ${range.end.line}) Format triggered for ${filePath}`); + Utilities.outputChannel.appendLine(`Range (Lines ${lineRange.start.line} to ${lineRange.end.line}) Format triggered for ${filePath}`); if (!Configuration.formatEnabled()) { const message = "Format not enabled in the settings. Skipping Format."; @@ -53,7 +69,7 @@ export class RangeFormattingProvider implements vscode.DocumentRangeFormattingEd const commandOptions: CommandOptions = { filePath: filePath, - fileContents: document.getText(range), + fileContents: document.getText(lineRange), }; const result = await SQLFluff.run( @@ -67,13 +83,18 @@ export class RangeFormattingProvider implements vscode.DocumentRangeFormattingEd throw new Error("Command failed to execute, check logs for details"); } - const lines = FormatHelper.parseLines(result.lines); + let lines = FormatHelper.parseLines(result.lines); + + const leadingWhitespace = document.lineAt(range.start.line).firstNonWhitespaceCharacterIndex; + lines = lines ? FormatHelper.addLeadingWhitespace(lines, document.languageId, leadingWhitespace) : undefined; + if (lines === undefined) return []; if (lines.length > 1 || lines[0] !== "") { + const eol = document.eol; textEdits.push(vscode.TextEdit.replace( - range, - lines.join("\n"), + lineRange, + eol === vscode.EndOfLine.LF ? lines.join("\n") : lines.join("\r\n"), )); } diff --git a/test/.vscode/settings.json b/test/.vscode/settings.json index 86c67be..57c6187 100644 --- a/test/.vscode/settings.json +++ b/test/.vscode/settings.json @@ -8,11 +8,11 @@ "sqlfluff.dialect": "postgres", "sqlfluff.format.languages": [ "sql", - "sql-bigquery", - "jinja-sql", - "postgres", - "snowflake-sql", - "markdown" + { + "language": "markdown" , + "contextMenuFormatOptions": false, + "preserveLeadingWhitespace": true + } ], /* Linter */ @@ -30,6 +30,6 @@ "sqlfluff.executablePath": "sqlfluff", // "sqlfluff.shell": true, // "sqlfluff.shell": "C:/Program Files/Git/bin/bash.exe", - "sqlfluff.workingDirectory": "${fileDirname}", + "sqlfluff.workingDirectory": "${fileDirname}" // "sqlfluff.workingDirectory": "broken", } diff --git a/test/suite/test_sql/markdown.md b/test/suite/test_sql/markdown.md index 2909fea..09a9fbf 100644 --- a/test/suite/test_sql/markdown.md +++ b/test/suite/test_sql/markdown.md @@ -1,9 +1,9 @@ ## SQL to be formated -``` sql -SELECT * -from EMP -JOIN DEPT ON EMP.DEPTNO = DEPT.DEPTNO; +```sql + SELECT * + FROM emp + INNER JOIN dept ON emp.deptno = dept.deptno; ``` ## SQL to be ignored @@ -14,7 +14,7 @@ SELECT * FROM EMP JOIN DEPT ON EMP.DEPTNO = DEPT.DEPTNO; ## JavaScript to be ignored -``` js +```js var foo = function (bar) { return bar++; };