diff --git a/package.json b/package.json index 979091a..6117e81 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "onCommand:extension.buildStringCsharp", "onCommand:extension.reverseBuildStringDoubleQuote", "onCommand:extension.reverseBuildStringSingleQuote", - "onCommand:extension.createCsharpAutoProperty" + "onCommand:extension.createCsharpAutoProperty", + "onCommand:extension.reverseDelphiQuery" ], "main": "./out/extension", "contributes": { @@ -69,6 +70,10 @@ { "command": "extension.createCsharpAutoProperty", "title": "Solidt: Create autoproperty (C#)" + }, + { + "command": "extension.reverseDelphiQuery", + "title": "Solidt: Reverse Query (Delphi)" } ] }, @@ -84,4 +89,4 @@ "typescript": "^3.5.1", "@types/vscode": "^1.32.0" } -} +} \ No newline at end of file diff --git a/src/delphi.ts b/src/delphi.ts new file mode 100644 index 0000000..c0452fc --- /dev/null +++ b/src/delphi.ts @@ -0,0 +1,155 @@ + +function* getLinesFromString(source: string) { + let lines = String(source || "").matchAll(/^.+$/gm); + for (const [line] of lines) yield line; +} + +interface IPart { + isString: boolean; + value: string; +} + +function* getNextStringPartFromLine(source: string, separator?: string): IterableIterator { + const line = String(source || ""); + const sep = "'"; + let inString = false; + let start = 0; + for (var i = 0; i < line.length; i++) { + const char = line[i]; + + // if (char === sep) { + // console.log("swithing in string"); + + // } + + if (i + 1 === line.length) { + yield { + isString: inString, + value: line.substring(start, i + 1) + }; + } else if (char === sep) { + yield { + isString: inString, + value: line.substring(start, i) + }; + start = i + 1; + inString = !inString; + } + } +} + +function trimAny(str: string, chars: string[]) { + var start = 0, + end = str.length; + + while (start < end && chars.indexOf(str[start]) >= 0) + ++start; + + while (end > start && chars.indexOf(str[end - 1]) >= 0) + --end; + + return (start > 0 || end < str.length) ? str.substring(start, end) : str; +} + + +const reSingleLineComment = /(\/\/.*)/g; +const reMultiLineComment = /\{(.|[\r\n])*?\}/gm; +const reEmptyOrBlankLines = /\n+\s*\n/gm; + +const reJoinPlusLinesEnd = /\s*\+\s*?\n/gm; +const reJoinPlusLinesStart = /\s*\n\s*?\+/gm; + +function cleanUpCode(str: string) { + str = String(str).replace(reSingleLineComment, ""); + str = String(str).replace(reMultiLineComment, ""); + str = String(str).replace(reEmptyOrBlankLines, "\n"); + + str = String(str).replace(reJoinPlusLinesStart, " + "); + str = String(str).replace(reJoinPlusLinesEnd, " + "); + + return str; +} + +export function reverseDelphiQuery(input: string): string { + const output: string[] = []; + input = cleanUpCode(input); + + for (const line of getLinesFromString(input)) { + + const outputParts: string[] = []; + + for (const part of getNextStringPartFromLine(line)) { + + if (part.isString) { + outputParts.push(part.value); + } else { + + const matches = part.value.matchAll(/[\+\(]\s*(.*)\s*[\+;]/gm); + for (const match of matches) { + const param = match[1]; + const rem = trimAny(param, [' ', '+', '\t', '\r']); + if (rem.length > 0) { + + const res = findStringBetweenMatchingBrace('(' + rem + ')', 0, '(', ')'); + if (res.trim().length > 0) { + outputParts.push(`[[${res}]]`); + } else { + outputParts.push(`{{${res}}}`); + } + } + } + + // const matches = part.value.matchAll(/\((.*)\)/gm); + // for (const match of matches) { + // const param = match[1]; + // const rem = trimAny(param, [' ', '+', '\t', '\r']); + // if (rem.length > 0) { + // outputParts.push(rem); + // } + // } + } + + //console.log(`part: ${part.isString ? "1" : "0"}`, part.value); + } + + output.push(outputParts.join(" ")); + } + return input + '\n\n' + output.join("\n"); +}; + +function indexOfAny(str: string, anyOf: string[], startIndex: number): number { + if (!str) return -1; + if (!anyOf) return -1; + let minIndex = -1; + for (let j = 0; j < anyOf.length; j++) { + let index = str.indexOf(anyOf[j], startIndex); + if (index >= 0 && (minIndex === -1 || index < minIndex)) { + minIndex = index; + } + } + return minIndex; +}; + +function findStringBetweenMatchingBrace(s: string, start: number, startBrace: string, endBrace: string) { + const braces = [startBrace, endBrace]; + let pos = start; + let end = start; + let level = 0; + let notStarted = true; + while (true) { + var index = indexOfAny(s, braces, pos); + if (index < 0) break; + if (notStarted) { + notStarted = false; + start = index + 1; + } + if (s[index] == startBrace) level++; + if (s[index] == endBrace) level--; + if (level <= 0) { + end = index; + break; + } + pos = index + 1; + } + return s.substring(start, end); +} \ No newline at end of file diff --git a/src/escape.ts b/src/escape.ts index c2e6c34..1a4ad4c 100644 --- a/src/escape.ts +++ b/src/escape.ts @@ -1,7 +1,7 @@ const slash = "\\"; const slashslash = "\\\\"; -function getEscapeChars(quote: string, doubleEscape: boolean) : { [key: string]: string } { +function getEscapeChars(quote: string, doubleEscape: boolean): { [key: string]: string } { const chars = { "\b": "\\b", "\n": "\\n", @@ -11,14 +11,14 @@ function getEscapeChars(quote: string, doubleEscape: boolean) : { [key: string]: slash: slashslash }; if (doubleEscape) { - (chars as any)[quote] = quote + quote; + (chars as any)[quote] = quote + quote; } else { (chars as any)[quote] = slash + quote; } return chars; } -function getUnescapeChars(quote: string, doubleEscape: boolean) : { [key: string]: string } { +function getUnescapeChars(quote: string, doubleEscape: boolean): { [key: string]: string } { const chars = { "\\b": "\b", "\\n": "\n", @@ -28,7 +28,7 @@ function getUnescapeChars(quote: string, doubleEscape: boolean) : { [key: string slashslash: slash }; if (doubleEscape) { - (chars as any)[quote + quote] = quote; + (chars as any)[quote + quote] = quote; } else { (chars as any)[slash + quote] = quote; } @@ -38,79 +38,82 @@ function getUnescapeChars(quote: string, doubleEscape: boolean) : { [key: string export function escapeStr(str: string, quote: string, doubleEscape: boolean): string { let res = ""; const chars = getEscapeChars(quote, doubleEscape) - for (const c of String(str)) { - if (c in chars) { - res += chars[c]; - } else { - res += c; - } - } - return res; + for (const c of String(str)) { + if (c in chars) { + res += chars[c]; + } else { + res += c; + } + } + return res; } export function unescapeStr(str: string, quote: string, doubleEscape: boolean): string { const chars = getUnescapeChars(quote, doubleEscape); let res = String(str).replace(/(\\.)/gm, (group) => { - if (group in chars) { - return chars[group]; - } - return group; + if (group in chars) { + return chars[group]; + } + return group; }); - if (doubleEscape) + if (doubleEscape) res = res.replace(quote + quote, quote); return res; } export function buildString(text: string, quote: string, doubleEscape: boolean): string { - var lines = text.split('\n'); - return lines.map(l => '\t' + quote + escapeStr(l, quote, doubleEscape) + quote).join(",\n"); + var lines = text.split('\n'); + return lines.map(l => '\t' + quote + escapeStr(l, quote, doubleEscape) + quote).join(",\n"); }; export function buildStringCsharp(text: string): string { - const prefix = "var str = string.Join(Environment.NewLine, new string[] {\n"; - const postfix = "\n});"; - return `${prefix}${buildString(text, '"', false)}${postfix}`; + const prefix = "var str = string.Join(Environment.NewLine, new string[] {\n"; + const postfix = "\n});"; + return `${prefix}${buildString(text, '"', false)}${postfix}`; }; export function buildStringJavascript(text: string): string { const prefix = "var str = [\n"; - const postfix = "\n].join('\\n');"; - return `${prefix}${buildString(text, '"', false)}${postfix}`; + const postfix = "\n].join('\\n');"; + return `${prefix}${buildString(text, '"', false)}${postfix}`; }; const getMatches = (re: RegExp, str: string): any[] => { - let match, matches = []; - while (match = re.exec(str)) { - matches.push(match); - } - return matches; + let match, matches = []; + while (match = re.exec(str)) { + matches.push(match); + } + return matches; }; export function reverseBuildString(text: string, quote: string, doubleEscape: boolean): string { - let regEx = new RegExp(quote + "(.*[^"+ slashslash +"])" + quote, "gm"); + let regEx = new RegExp(quote + "(.*[^" + slashslash + "])" + quote, "gm"); if (doubleEscape) { - regEx = new RegExp(quote + "(.*[^" + quote + "])" + quote, "gm"); + regEx = new RegExp(quote + "(.*[^" + quote + "])" + quote, "gm"); } - return getMatches(regEx, text) - .map(m => unescapeStr(m[1], quote, doubleEscape)) - .join("\n"); + return getMatches(regEx, text) + .map(m => unescapeStr(m[1], quote, doubleEscape)) + .join("\n"); }; function lowerFirst(input: string) { return input.charAt(0).toLowerCase() + input.slice(1) -} -export function createCsharpAutoProperty(input: string) -{ - const [type, name] = input.split(" "); - const pname = "_" + lowerFirst(name); - const output = [ - `private ${type} ${pname};`, - `public ${type} ${name}`, - "{", - `\tget => ${pname};`, - `\tset => SetPropertyValue(nameof(${name}), ref ${pname}, value);`, - "}" - ].join("\n"); - return output; +} +export function createCsharpAutoProperty(input: string) { + const lines = input.split('\n'); + const output = []; + for (const line of lines) { + const [type, name] = line.split(" ").map(x => x.trim()); + const pname = "_" + lowerFirst(name); + output.push([ + `private ${type} ${pname};`, + `public ${type} ${name}`, + "{", + `\tget => ${pname};`, + `\tset => SetPropertyValue(nameof(${name}), ref ${pname}, value);`, + "}" + ].join("\n")); + } + return output.join("\n\n"); }; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index ee9b84b..174ccce 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,22 +1,21 @@ 'use strict'; import * as vscode from 'vscode'; +import { reverseDelphiQuery } from "./delphi"; import { escapeStr, unescapeStr, buildString, reverseBuildString, buildStringJavascript, buildStringCsharp, createCsharpAutoProperty } from "./escape"; type Replacer = (a: string) => string; -function run(replacer: Replacer) -{ +function run(replacer: Replacer) { // Get the active text editor - let editor = vscode.window.activeTextEditor; + const editor = vscode.window.activeTextEditor; - if (editor) { - let document = editor.document; - let selection = editor.selection; - let selectedText = document.getText(selection); - let result = replacer(selectedText) + if (editor && editor.document && editor.selection) { + const selectedText = editor.document.getText(editor.selection); + if (String(selectedText).length <= 0) return; + const result = replacer(selectedText); editor.edit(editBuilder => { - editBuilder.replace(selection, result); + editBuilder.replace(editor!.selection, result); }); } } @@ -27,15 +26,16 @@ const escapeSingleQuote = (str: string): string => escapeStr(str, "'", false); // const escapeDoubleQuoteDoubled = (str: string): string => escapeStr(str, '"', true); // const escapeSingleQuoteDoubled = (str: string): string => escapeStr(str, "'", true); -const unescapeDoubleQuote = (str: string): string => unescapeStr(str, '"', false); +const unescapeDoubleQuote = (str: string): string => unescapeStr(str, '"', false); const unescapeSingleQuote = (str: string): string => unescapeStr(str, "'", false); -const buildStringDoubleQuote = (str: string): string => buildString(str, '"', false); +const buildStringDoubleQuote = (str: string): string => buildString(str, '"', false); const buildStringSingleQuote = (str: string): string => buildString(str, "'", false); -const reverseBuildStringDoubleQuote = (str: string): string => reverseBuildString(str, '"', false); +const reverseBuildStringDoubleQuote = (str: string): string => reverseBuildString(str, '"', false); const reverseBuildStringSingleQuote = (str: string): string => reverseBuildString(str, "'", false); + export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('extension.escapeDoubleQuote', () => run(escapeDoubleQuote))); context.subscriptions.push(vscode.commands.registerCommand('extension.escapeSingleQuote', () => run(escapeSingleQuote))); @@ -53,6 +53,5 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('extension.reverseBuildStringSingleQuote', () => run(reverseBuildStringSingleQuote))); context.subscriptions.push(vscode.commands.registerCommand('extension.createCsharpAutoProperty', () => run(createCsharpAutoProperty))); - - + context.subscriptions.push(vscode.commands.registerCommand('extension.reverseDelphiQuery', () => run(reverseDelphiQuery))); } \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 4ce5274..8deb67f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,14 @@ { "compilerOptions": { "module": "commonjs", - "target": "es6", + "target": "es2020", "outDir": "out", "sourceMap": true, "strict": true, "rootDir": "src" }, - "exclude": ["node_modules", ".vscode-test"] -} + "exclude": [ + "node_modules", + ".vscode-test" + ] +} \ No newline at end of file