import { DocumentFormattingEditProvider , TextDocument , FormattingOptions , CancellationToken , ProviderResult , TextEdit , DocumentRangeFormattingEditProvider , Range , Position , workspace , window , } from "vscode" ; import { FormattingRules , RuleMatch , getStructsNoIdent , StructureRule , } from "./formmatingRules" ; import * as sqlFormatterPlus from "sql-formatter-plus" ; import * as nls from 'vscode-nls' ; const localize = nls . loadMessageBundle ( ) ; const structsNoIdent = getStructsNoIdent ( ) ; class Formatting implements DocumentFormattingEditProvider { lineContinue = false ; provideDocumentFormattingEdits ( document , options , token ) { const formatter = new RangeFormatting ( ) ; const range = new Range ( new Position ( 0 , 0 ) , new Position ( document . lineCount - 1 , 0 ) ) ; return formatter . provideDocumentRangeFormattingEdits ( document , range , options , token ) ; } } class RangeFormatting implements DocumentRangeFormattingEditProvider { lineContinue = false ; provideDocumentRangeFormattingEdits ( document , range , options , token ) { let noQueryFormatter = workspace . getConfiguration ( ) . get ( "advplformat.noQueryFormatter" ) ; let queryLanguage = workspace . getConfiguration ( ) . get ( "advplformat.queryLanguage" ) ; if ( ! queryLanguage && ! noQueryFormatter ) { askSqlLanguage ( ) ; noQueryFormatter = workspace . getConfiguration ( ) . get ( "advplformat.noQueryFormatter" ) ; queryLanguage = workspace . getConfiguration ( ) . get ( "advplformat.queryLanguage" ) ; } queryLanguage = queryLanguage ? queryLanguage : "sql" ; let cont = 0 ; let query ; const tab = options . insertSpaces ? " " . repeat ( options . tabSize ) : "\t" ; if ( range . start . line > 0 ) { let stringStart = document . lineAt ( range . start . line ) . text . replace ( /(^\s*)(.*)/ , "$1" ) ; while ( stringStart . search ( tab ) >= 0 ) { cont ++ ; stringStart = stringStart . replace ( tab , "" ) ; } } const formattingRules = new FormattingRules ( ) ; const result = [ ] ; const lc = range . end . line ; const rulesIgnored = formattingRules . getStructures ( ) . filter ( ( rule ) => { return structsNoIdent . indexOf ( rule . id ) !== - 1 ; } ) ; for ( let nl = range . start . line ; nl <= lc ; nl ++ ) { if ( token . isCancellationRequested ) { console . log ( "cancelado" ) ; return [ ] ; } const line = document . lineAt ( nl ) ; const text = line . text . trimRight ( ) ; const lastRule = formattingRules . openStructures [ formattingRules . openStructures . length - 1 ] ; const foundIgnore = rulesIgnored . filter ( ( rule ) => { return lastRule && lastRule . rule && rule . id === lastRule . rule . id ; } ) ; if ( foundIgnore . length > 0 && ! text . match ( foundIgnore [ 0 ] . end ) ) { if ( ! noQueryFormatter && foundIgnore [ 0 ] . id === "beginsql (alias)?" ) { if ( ! query || query . expression . length === 0 ) { query = { expression : "" , range : line . range } ; } query . expression += " " + text . replace ( "//" , "--REPLACE--" ) . replace ( /^\s*/gim , "" ) + "\n" ; query . expression = query . expression . replace ( /'\\'/gim , "'******\\******'" ) ; query . range = new Range ( query . range . start , line . range . end ) ; } else { if ( document . lineAt ( nl ) . text !== text ) { result . push ( TextEdit . replace ( line . range , text ) ) ; } } } else { if ( ! line . isEmptyOrWhitespace && ! this . lineContinue && formattingRules . match ( text , cont ) ) { const ruleMatch = formattingRules . getLastMatch ( ) ; if ( ruleMatch ) { if ( ruleMatch . decrement ) { cont = ruleMatch . initialPosition ; if ( ! noQueryFormatter && ruleMatch . rule . id === "beginsql (alias)?" ) { let queryResult = sqlFormatterPlus . format ( query . expression , { indent : tab , language : queryLanguage } ) ; queryResult = queryResult . replace ( /--REPLACE--/gim , "//" ) ; queryResult = tab . repeat ( cont + 1 ) + queryResult . replace ( /\n/gim , "\n" + tab . repeat ( cont + 1 ) ) ; queryResult = queryResult . replace ( /(%)\s+(table|temp-table|exp|xfilial|order)\s*(:)((\w|\+|\\|\*|\(|\)|\[|\]|-|>|_|\s|,|\n|"|')*)\s+(:*\w*)\s*(%)/gim , "$1$2$3$4$6$7" ) ; while ( queryResult . match ( /(%(table|temp-table|exp|xfilial|order):.+)\s*\[\s+(.+%)/gim ) ) { queryResult = queryResult . replace ( /(%(table|temp-table|exp|xfilial|order):.+)\s*\[\s+(.+%)/gim , "$1[$3" ) ; } while ( queryResult . match ( /(%(table|temp-table|exp|xfilial|order):.+)\s+\]\s*(.*%)/gim ) ) { queryResult = queryResult . replace ( /(%(table|temp-table|exp|xfilial|order):.+)\s+\]\s*(.*%)/gim , "$1]$3" ) ; } queryResult = queryResult . replace ( /(%order:\w*)(,\n\s*)(\w%)/gim , "$1,$3" ) ; queryResult = queryResult . replace ( /(%)(\s+)(notDel|noparser)(\s+)(%)/gim , "$1$3$5" ) ; queryResult = queryResult . replace ( /\.\s(%notDel%)/gim , ".$1" ) ; queryResult = queryResult . replace ( /((\s*)%noparser%)\s+/gim , "$1\n\n$2" ) ; if ( queryResult . match ( /(.*)(\n\s*select)/im ) ) { let columnsText = queryResult . match ( /(.*)(\n\s*select)/im ) [ 0 ] . replace ( /(.*)(\n\s*select)/im , "$1" ) ; const tabColumn = columnsText . replace ( /column\s.*/gim , "" ) ; if ( tabColumn ) { columnsText = columnsText . replace ( /(\s*)(column\s[0-z]*\sas\s[0-z]*)/gim , "\n" + tabColumn + "$2" ) ; columnsText = columnsText . replace ( /\n/ , "" ) ; queryResult = queryResult . replace ( /(.*)(\n\s*select)/im , columnsText + "$2" ) ; } } queryResult = queryResult . replace ( /\s*->\s*/gim , "->" ) ; queryResult = queryResult . replace ( /\s*\[\s*/gim , "[" ) ; while ( queryResult . match ( /(%.*)(\s+)(,|\+|-|\\|\*)(\s*)(.*%)/gim ) ) { queryResult = queryResult . replace ( /(%.*)(\s+)(,|\+|-|\\|\*)(\s*)(.*%)/gim , "$1$3$5" ) ; } while ( queryResult . match ( /(%.*)(\s*)(,|\+|-|\\|\*)(\s+)(.*%)/gim ) ) { queryResult = queryResult . replace ( /(%.*)(\s*)(,|\+|-|\\|\*)(\s+)(.*%)/gim , "$1$3$5" ) ; } queryResult = queryResult . replace ( /(^\s*.*between\s*.*)\n\s*(and\s.*)/gim , "$1 $2" ) ; queryResult = queryResult . replace ( /(^(\s*)(.*join\s*.*\s|\)\s\w*\s))(on(\s|\n))/gim , "$1\n$2$4" ) ; queryResult = queryResult . replace ( /(^(\s*)when.*)\n*\s*(then.*)/gim , "$1\n$2" + tab + "$3" ) ; queryResult = queryResult . replace ( /\s*(\n\s*(on|join|when|then))/gim , "$1" ) ; if ( queryResult . match ( /^\s*(\w*\sjoin|on)\s/gim ) ) { const queryLines = queryResult . split ( "\n" ) ; queryResult = "" ; queryLines . forEach ( ( line ) => { if ( line . match ( /^\s*(\w*\sjoin|on)\s/gim ) ) { queryResult += line . replace ( tab , "" ) + "\n" ; } else { queryResult += line + "\n" ; } } ) ; } queryResult = queryResult . replace ( /\n\s*(\*)/gim , " $1" ) ; queryResult = queryResult . replace ( /'\*\*\*\*\*\*\\\*\*\*\*\*\*'/gim , "'\\'" ) ; queryResult = queryResult . replace ( /(^(\s*).*,\n)\s*(case)/gim , "$1$2$3" ) ; result . push ( TextEdit . replace ( query . range , queryResult . trimEnd ( ) ) ) ; query = { expression : "" , range : undefined } ; } } } if ( ruleMatch . incrementDouble ) { cont += 1 ; } const newLine = text . replace ( /(\s*)?/ , tab . repeat ( cont ) ) ; if ( document . lineAt ( nl ) . text !== newLine ) { result . push ( TextEdit . replace ( line . range , newLine ) ) ; } this . lineContinue = newLine . split ( "//" ) [ 0 ] . trim ( ) . endsWith ( ";" ) && rulesIgnored . indexOf ( ruleMatch . rule ) === - 1 ; if ( ruleMatch ) { if ( ruleMatch . increment || ruleMatch . incrementDouble ) { cont ++ ; } } } else { let newLine = "" ; if ( ! line . isEmptyOrWhitespace ) { newLine = text . replace ( /(\s*)?/ , tab . repeat ( cont ) + ( this . lineContinue ? tab : "" ) ) . trimRight ( ) ; } if ( document . lineAt ( nl ) . text !== newLine ) { result . push ( TextEdit . replace ( line . range , newLine ) ) ; } this . lineContinue = newLine . split ( "//" ) [ 0 ] . trim ( ) . endsWith ( ";" ) ; } } } return result ; } } export function askSqlLanguage ( ) { const sqlLaguages = [ { sigla : "sql" , descricao : "Standard SQL" } , { sigla : "n1ql" , descricao : "Couchbase N1QL" } , { sigla : "db2" , descricao : "IBM DB2" } , { sigla : "pl/sql" , descricao : "Oracle PL/SQL" } , ] ; const workspaceFolders = workspace [ "workspaceFolders" ] ; if ( workspaceFolders === undefined ) { return ; } const quetionArray = sqlLaguages . map ( ( lang ) => { return lang . descricao ; } ) ; quetionArray . push ( localize ( "formatting.query.disable" , "Disable" ) ) ; const defaultConfig = workspace . getConfiguration ( ) ; window . showWarningMessage ( localize ( "formatting.query.question" , "Do you want to enable query formatting for:" ) , ... quetionArray ) . then ( ( clicked ) => { if ( clicked === localize ( "formatting.query.disable" , "Disable" ) ) { defaultConfig . update ( "advplformat.noQueryFormatter" , true ) ; } else { const config = sqlLaguages . find ( ( lang ) => { return lang . descricao === clicked ; } ) ; if ( config ) { defaultConfig . update ( "advplformat.noQueryFormatter" , false ) ; defaultConfig . update ( "advplformat.queryLanguage" , config . sigla ) ; } } } ) ; } const formatter = new Formatting ( ) ; const rangeFormatter = new RangeFormatting ( ) ; export function formattingEditProvider ( ) { return formatter ; } export function rangeFormattingEditProvider ( ) { return rangeFormatter ; }