diff --git a/package.json b/package.json index cdb2ebf..9983e02 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,26 @@ "onDebugResolve:PowerShell" ], "main": "./out/extension.js", - "contributes": {}, + "contributes": { + "configuration": { + "title": "Inline Values support for PowerShell", + "properties": { + "powershellInlineValues.startLocation": { + "type": "string", + "default": "currentFunction", + "enum": [ + "currentFunction", + "document" + ], + "enumDescriptions": [ + "Start of current function. Defaults to top of document if not stopped inside function.", + "Always from top of document. Default before 0.0.7." + ], + "description": "Specifies the start position for inline values while debugging. Inline values will be shown from selected start positiion until stopped location." + } + } + } + }, "scripts": { "vscode:prepublish": "yarn run compile", "compile": "tsc -p ./", @@ -49,4 +68,4 @@ "publisherId": "2d97a8b2-cb9f-4729-9fca-51d9cea8f5dc", "isPreReleaseVersion": false } -} \ No newline at end of file +} diff --git a/src/documentParser.ts b/src/documentParser.ts new file mode 100644 index 0000000..2093b8d --- /dev/null +++ b/src/documentParser.ts @@ -0,0 +1,77 @@ +import * as vscode from 'vscode'; +import * as utils from './utils'; + +export class DocumentParser { + // Used to avoid calling symbol provider for the same document on every stopped location + private readonly functionCache: Map = new Map(); + + // Clear cache between debugsessions to get updated symbols + clearFunctionCache(): void { + this.functionCache.clear(); + } + + async getFunctionsInScope(document: vscode.TextDocument, stoppedLocation: vscode.Range): Promise { + const functions = await this.getFunctionsInDocument(document); + const stoppedStart = stoppedLocation.start.line; + const stoppedEnd = stoppedLocation.end.line; + const res: vscode.DocumentSymbol[] = []; + + for (var i = 0, length = functions.length; i < length; ++i) { + const func = functions[i]; + // Only return functions with stopped location inside range + if (func.range.start.line <= stoppedStart && func.range.end.line >= stoppedEnd && func.range.contains(stoppedLocation)) { + res.push(func); + } + } + + return res; + } + + async getFunctionsInDocument(document: vscode.TextDocument): Promise { + const cacheKey = document.uri.toString(); + if (this.functionCache.has(cacheKey)) { + return this.functionCache.get(cacheKey)!; + } + + const documentSymbols = await vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri); + let functions: vscode.DocumentSymbol[] = []; + + if (documentSymbols) { + // Get all functions in a flat array from the symbol-tree + functions = utils.flattenSymbols(documentSymbols).filter(s => s.kind === vscode.SymbolKind.Function); + } + + this.functionCache.set(cacheKey, functions); + return functions; + } + + async getStartLine(document: vscode.TextDocument, startLocationSetting: string, stoppedLocation: vscode.Range): Promise { + if (startLocationSetting === 'document') { + return 0; + } + + // Lookup closest matching function start line or default to document start (0) + const functions = await this.getFunctionsInScope(document, stoppedLocation); + return Math.max(0, ...functions.map(fn => fn.range.start.line)); + } + + async getExcludedLines(document: vscode.TextDocument, stoppedLocation: vscode.Range, startLine: number): Promise> { + const functions = await this.getFunctionsInDocument(document); + const stoppedEnd = stoppedLocation.end.line; + const excludedLines = []; + + for (var i = 0, length = functions.length; i < length; ++i) { + const func = functions[i]; + // StartLine (either document start or closest function start) are provided, so functions necessary to exclude + // will always start >= documentStart or same as currentFunction start if nested function. + // Don't bother checking functions before startLine or after stoppedLocation + if (func.range.start.line >= startLine && func.range.start.line <= stoppedEnd && !func.range.contains(stoppedLocation)) { + const functionRange = utils.range(func.range.start.line, func.range.end.line); + excludedLines.push(...functionRange); + } + } + + // Ensure we don't exclude our stopped location and make lookup blazing fast + return new Set(excludedLines.filter(line => line < stoppedLocation.start.line || line > stoppedEnd)); + } +} diff --git a/src/extension.ts b/src/extension.ts index d7b4892..85e6454 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,20 @@ import * as vscode from 'vscode'; import { PowerShellVariableInlineValuesProvider } from './powerShellVariableInlineValuesProvider'; +import { DocumentParser } from './documentParser'; export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.languages.registerInlineValuesProvider('powershell', new PowerShellVariableInlineValuesProvider())); + const parser = new DocumentParser(); + + context.subscriptions.push(vscode.languages.registerInlineValuesProvider('powershell', new PowerShellVariableInlineValuesProvider(parser))); + + // Clear function symbol cache to ensure we get symbols from any updated files + context.subscriptions.push( + vscode.debug.onDidTerminateDebugSession((e) => { + if (e.type.toLowerCase() === 'powershell') { + parser.clearFunctionCache(); + } + }) + ); } export function deactivate() { } diff --git a/src/powerShellVariableInlineValuesProvider.ts b/src/powerShellVariableInlineValuesProvider.ts index 7602623..d601c92 100644 --- a/src/powerShellVariableInlineValuesProvider.ts +++ b/src/powerShellVariableInlineValuesProvider.ts @@ -1,26 +1,43 @@ import * as vscode from 'vscode'; +import { DocumentParser } from './documentParser'; export class PowerShellVariableInlineValuesProvider implements vscode.InlineValuesProvider { // Known constants - private readonly knownConstants = /^\$(?:true|false|null)$/i; + private readonly knownConstants = ['$true', '$false', '$null']; // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes?view=powershell-5.1#scope-modifiers - private readonly supportedScopes = /^(?:global|local|script|private|using|variable)$/i; + private readonly supportedScopes = ['global', 'local', 'script', 'private', 'using', 'variable']; // Variable patterns // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_variables?view=powershell-5.1#variable-names-that-include-special-characters - private readonly alphanumChars = /(?:\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nd}|[_?])/.source; + private readonly alphanumChars = /(?:\p{Ll}|\p{Lu}|\p{Nd}|[_?]|\p{Lt}|\p{Lm}|\p{Lo})/.source; private readonly variableRegex = new RegExp([ '(?:\\$\\{(?.*?)(? { + private readonly documentParser: DocumentParser; + + constructor(documentParser: DocumentParser) { + this.documentParser = documentParser; + } + + async provideInlineValues(document: vscode.TextDocument, viewport: vscode.Range, context: vscode.InlineValueContext): Promise { const allValues: vscode.InlineValue[] = []; - for (let l = 0; l <= context.stoppedLocation.end.line; l++) { + const extensionSettings = vscode.workspace.getConfiguration('powershellInlineValues'); + const startLocationSetting = extensionSettings.get('startLocation') ?? 'currentFunction'; + const startLine = await this.documentParser.getStartLine(document, startLocationSetting, context.stoppedLocation); + const endLine = context.stoppedLocation.end.line; + const excludedLines = await this.documentParser.getExcludedLines(document, context.stoppedLocation, startLine); + + for (let l = startLine; l <= endLine; l++) { + // Exclude lines out of scope (other functions) + if (excludedLines.has(l)) { + continue; + } + const line = document.lineAt(l); // Skip over comments @@ -39,7 +56,7 @@ export class PowerShellVariableInlineValuesProvider implements vscode.InlineValu if (colon !== -1) { // If invalid scope, ignore const scope = varName.substring(1, colon); - if (!this.supportedScopes.test(scope)) { + if (!this.supportedScopes.includes(scope.toLowerCase())) { continue; } @@ -47,7 +64,7 @@ export class PowerShellVariableInlineValuesProvider implements vscode.InlineValu } // If known PowerShell constant, ignore - if (this.knownConstants.test(varName)) { + if (this.knownConstants.includes(varName.toLowerCase())) { continue; } @@ -55,7 +72,6 @@ export class PowerShellVariableInlineValuesProvider implements vscode.InlineValu allValues.push(new vscode.InlineValueVariableLookup(rng, varName, false)); } } - return allValues; } } diff --git a/src/test/runTest.ts b/src/test/runTest.ts index f9266fa..0cd85b5 100644 --- a/src/test/runTest.ts +++ b/src/test/runTest.ts @@ -1,6 +1,6 @@ import * as path from 'path'; - -import { runTests } from 'vscode-test'; +import * as cp from 'child_process'; +import { runTests, downloadAndUnzipVSCode, resolveCliPathFromVSCodeExecutablePath } from 'vscode-test'; async function main() { try { @@ -12,8 +12,19 @@ async function main() { // Passed to --extensionTestsPath const extensionTestsPath = path.resolve(__dirname, './suite/index'); - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath, version: 'stable' }); + // Download VS Code and unzip it + const vscodeExecutablePath = await downloadAndUnzipVSCode('stable'); + const cliPath = resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath); + + // Use cp.spawn / cp.exec for custom setup. + // Need powershell extension for document symbol provider + cp.spawnSync(cliPath, ['--install-extension', 'ms-vscode.powershell'], { + encoding: 'utf-8', + stdio: 'inherit' + }); + + // Run tests using custom vscode setup + await runTests({ vscodeExecutablePath, extensionDevelopmentPath, extensionTestsPath }); } catch (err) { console.error(err); console.error('Failed to run tests'); diff --git a/src/test/suite/documentParser.test.ts b/src/test/suite/documentParser.test.ts new file mode 100644 index 0000000..e047498 --- /dev/null +++ b/src/test/suite/documentParser.test.ts @@ -0,0 +1,198 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { DocumentParser } from '../../documentParser'; + +suite('DocumentParser tests', async () => { + const parser: DocumentParser = new DocumentParser(); + let docSample1: vscode.TextDocument; + + suiteSetup(async () => { + docSample1 = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b # Stopped location + } + $a +} +$b +$notfound +`, + }); + }); + + suite('getFunctionsInScope() function', async () => { + test('returns only functions with stoppedLocation in range', async () => { + const result = await parser.getFunctionsInScope(docSample1, new vscode.Range(4, 8, 4, 10)); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].name, 'test1 { }'); + assert.strictEqual(result[0].kind, vscode.SymbolKind.Function); + assert.strictEqual(result[1].name, 'test2 { }'); + assert.strictEqual(result[1].kind, vscode.SymbolKind.Function); + }); + }); + + suite('getFunctionsInDocument() function', async () => { + test('returns all functions in document', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b + } + $a +} +$b # Stopped location +function test3 { } +$notfound +`, + }); + + const result = await parser.getFunctionsInDocument(doc); + assert.strictEqual(result.length, 3); + assert.strictEqual(result[0].name, 'test1 { }'); + assert.strictEqual(result[0].kind, vscode.SymbolKind.Function); + assert.strictEqual(result[1].name, 'test2 { }'); + assert.strictEqual(result[1].kind, vscode.SymbolKind.Function); + assert.strictEqual(result[2].name, 'test3 { }'); + assert.strictEqual(result[2].kind, vscode.SymbolKind.Function); + }); + + test('returns empty array when no functions exist', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +$b # Stopped location +$notfound +`, + }); + + const result = await parser.getFunctionsInDocument(doc); + assert.strictEqual(result.length, 0); + }); + }); + + suite('getStartLine() function', async () => { + test('returns linenumber for closest function start when stopped inside function', async () => { + const result = await parser.getStartLine(docSample1, 'currentFunction', new vscode.Range(4, 8, 4, 10)); + assert.strictEqual(result, 3); + }); + + test('returns document start when startLocationSetting is set to document when stopped inside function', async () => { + const result = await parser.getStartLine(docSample1, 'document', new vscode.Range(4, 8, 4, 10)); + assert.strictEqual(result, 0); + }); + + test('returns document start when not stopped inside function', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b + } + $a +} +$b # Stopped location +$notfound +`, + }); + + const result = await parser.getStartLine(doc, 'currentFunction', new vscode.Range(8, 0, 8, 3)); + assert.strictEqual(result, 0); + }); + }); + + suite('getExcludedLines() function', async () => { + test('returns array of lines for all functions outside scope of stopped location', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a +function test1 { + $b + $notfound +} + +function test2 { + $helloWorld +} + +$a # Stopped location + +test1 +`, + }); + + const result = await parser.getExcludedLines(doc, new vscode.Range(11, 0, 11, 3), 0); + assert.strictEqual(result.size, 7); + assert.strictEqual(result.has(2), true); + assert.strictEqual(result.has(3), true); + assert.strictEqual(result.has(4), true); + assert.strictEqual(result.has(5), true); + assert.strictEqual(result.has(7), true); + assert.strictEqual(result.has(8), true); + assert.strictEqual(result.has(9), true); + }); + + test('returns empty array when no functions out of scope are present in range', async () => { + // range = startline (test2 function start) -> stopped location + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a +function test1 { + $b + $notfound +} + +function test2 { + $b # Stopped location +} +$a + +test2 +`, + }); + + const result = await parser.getExcludedLines(doc, new vscode.Range(8, 4, 8, 7), 7); + assert.strictEqual(result.size, 0); + }); + + test('does not exclude stoppedLocation multi-range', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +function test2 { + $b +}; $param = @{ # Stopped location + Abc = 123 # Stopped location +} # Stopped location +`, + }); + + const result = await parser.getExcludedLines(doc, new vscode.Range(3, 3, 5, 1), 0); + assert.strictEqual(result.size, 2); + assert.strictEqual(result.has(1), true); + assert.strictEqual(result.has(2), true); + }); + + test('does not exclude stoppedLocation line when same as out of scope function', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +function test1 { }; $b; function test2 { }; #$b is stoppedLocation +`, + }); + + const result = await parser.getExcludedLines(doc, new vscode.Range(1, 20, 1, 25), 0); + assert.strictEqual(result.size, 0); + }); + }); +}); diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts deleted file mode 100644 index 3eb485d..0000000 --- a/src/test/suite/extension.test.ts +++ /dev/null @@ -1,474 +0,0 @@ -import * as assert from 'assert'; - -import * as vscode from 'vscode'; -import { PowerShellVariableInlineValuesProvider } from '../../powerShellVariableInlineValuesProvider'; - -suite('Variable detection', async () => { - vscode.window.showInformationMessage('Start all tests.'); - - test('Misc variable tests', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -$normal = Get-Process -$script:scoped = 5 -$numb3rInside = 'asdf' -$33333 = 'numbers' -$normal, \${braces}, $script:scoped -4 -`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 7); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i + 1; - switch (i) { - case 0: - name = '$normal'; - break; - case 1: - name = '$scoped'; - break; - case 2: - name = '$numb3rInside'; - break; - case 3: - name = '$33333'; - break; - case 4: - name = '$normal'; - break; - case 5: - name = '$braces'; - startChar = 9; - line = 5; - break; - case 6: - name = '$scoped'; - startChar = 20; - line = 5; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); - - test('Known constants ignored', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: '$false $true $null $123', - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 1); - - // Making sure the test actually ran by including a real variable - const variable = result![0] as vscode.InlineValueVariableLookup; - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, 0); - assert.strictEqual(variable.range.end.line, 0); - assert.strictEqual(variable.range.start.character, 19); - assert.strictEqual(variable.variableName, '$123'); - assert.strictEqual(variable.range.end.character, (19 + 4)); - }); - - test('Alphanumerical variables', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -$normal = Get-Process -$numb3rInside = 'asdf' -$33333 = 'numbers' -$something_wrong? = 123 -4 -`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 4); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i + 1; - switch (i) { - case 0: - name = '$normal'; - break; - case 1: - name = '$numb3rInside'; - break; - case 2: - name = '$33333'; - break; - case 3: - name = '$something_wrong?'; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); - - test('Scoped variables', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -$script:scoped = 5 -$global:scoped = 5 -$local:scoped = 5 -$using:scoped = 5 -$private:scoped = 5 -$variable:scoped = 5 -\${Script:special scoped} -$invalidscope:notdetected = 123 -4 -`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 7); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i + 1; - switch (i) { - case 0: - name = '$scoped'; - break; - case 1: - name = '$scoped'; - break; - case 2: - name = '$scoped'; - break; - case 3: - name = '$scoped'; - break; - case 4: - name = '$scoped'; - break; - case 5: - name = '$scoped'; - break; - case 6: - name = '$special scoped'; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); - - test('Special character variables', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -\${hello\`\`b} -\${braces} = "asdf" -\${ } = 'spaces' -\${Script:omg\`b} -\${bra%!c\\e\`} { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: `$sb = {\${hello \`{ \`} world}}`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 2); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i; - switch (i) { - case 0: - name = '$sb'; - break; - case 1: - name = '$hello { } world'; - startChar = 7; - line = 0; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); - - test('Variables used with collections', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -$dict[$key] -@($element,$element2, $element3) -$normal, \${braces}, $script:scoped -@{key = $var} -@{key = \${special var}} -`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 10); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i + 1; - switch (i) { - case 0: - name = '$dict'; - break; - case 1: - name = '$key'; - startChar = 6; - line = 1; - break; - case 2: - name = '$element'; - startChar = 2; - line = 2; - break; - case 3: - name = '$element2'; - startChar = 11; - line = 2; - break; - case 4: - name = '$element3'; - startChar = 22; - line = 2; - break; - case 5: - name = '$normal'; - line = 3; - break; - case 6: - name = '$braces'; - startChar = 9; - line = 3; - break; - case 7: - name = '$scoped'; - startChar = 20; - line = 3; - break; - case 8: - name = '$var'; - startChar = 8; - line = 4; - break; - case 9: - name = '$special var'; - startChar = 8; - line = 5; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); - - test('Variables with modifiers', async () => { - const doc = await vscode.workspace.openTextDocument({ - language: 'powershell', - content: ` -$a; -$a,$b -$a-$b -$a+$b -$a/$b -$a*$b -`, - }); - - const provider = new PowerShellVariableInlineValuesProvider(); - - const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { - stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), - frameId: 0 - }); - - assert.strictEqual(result?.length, 11); - for (let i = 0; i < result.length; i++) { - const variable = result![i] as vscode.InlineValueVariableLookup; - - let name: string = ''; - let startChar: number = 0; - let line: number = i + 1; - switch (i) { - case 0: - name = '$a'; - break; - case 1: - name = '$a'; - break; - case 2: - name = '$b'; - startChar = 3; - line = 2; - break; - case 3: - name = '$a'; - line = 3; - break; - case 4: - name = '$b'; - startChar = 3; - line = 3; - break; - case 5: - name = '$a'; - line = 4; - break; - case 6: - name = '$b'; - startChar = 3; - line = 4; - break; - case 7: - name = '$a'; - line = 5; - break; - case 8: - name = '$b'; - startChar = 3; - line = 5; - break; - case 9: - name = '$a'; - line = 6; - break; - case 10: - name = '$b'; - startChar = 3; - line = 6; - break; - } - - assert.strictEqual(variable.caseSensitiveLookup, false); - assert.strictEqual(variable.range.start.line, line); - assert.strictEqual(variable.range.end.line, line); - assert.strictEqual(variable.range.start.character, startChar); - assert.strictEqual(variable.variableName, name); - assert.strictEqual(variable.range.end.character, name.length + startChar); - } - }); -}); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts index 7029e38..d66f35f 100644 --- a/src/test/suite/index.ts +++ b/src/test/suite/index.ts @@ -1,8 +1,9 @@ import * as path from 'path'; import * as Mocha from 'mocha'; import * as glob from 'glob'; +import { ensureEditorServicesIsConnected } from '../testUtils'; -export function run(): Promise { +export async function run(): Promise { // Create the mocha test const mocha = new Mocha({ ui: 'tdd', @@ -11,6 +12,9 @@ export function run(): Promise { const testsRoot = path.resolve(__dirname, '..'); + // Ensure PowerShell extension is finished starting because we need it's symbol provider + await ensureEditorServicesIsConnected(); + return new Promise((c, e) => { glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { if (err) { diff --git a/src/test/suite/powerShellVariableInlineValuesProvider.test.ts b/src/test/suite/powerShellVariableInlineValuesProvider.test.ts new file mode 100644 index 0000000..0becbd5 --- /dev/null +++ b/src/test/suite/powerShellVariableInlineValuesProvider.test.ts @@ -0,0 +1,755 @@ +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { PowerShellVariableInlineValuesProvider } from '../../powerShellVariableInlineValuesProvider'; +import { DocumentParser } from '../../documentParser'; + +suite('PowerShellVariableInlineValuesProvider tests', async () => { + suite('Variable detection', async () => { + const provider: PowerShellVariableInlineValuesProvider = new PowerShellVariableInlineValuesProvider(new DocumentParser()); + + test('Misc variable tests', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$normal = Get-Process +$script:scoped = 5 +$numb3rInside = 'asdf' +$33333 = 'numbers' +$normal, \${braces}, $script:scoped +4 +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 7); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$normal'; + break; + case 1: + name = '$scoped'; + break; + case 2: + name = '$numb3rInside'; + break; + case 3: + name = '$33333'; + break; + case 4: + name = '$normal'; + break; + case 5: + name = '$braces'; + startChar = 9; + line = 5; + break; + case 6: + name = '$scoped'; + startChar = 20; + line = 5; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('Known constants ignored', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: '$false $true $null $123', + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 1); + + // Making sure the test actually ran by including a real variable + const variable = result![0] as vscode.InlineValueVariableLookup; + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, 0); + assert.strictEqual(variable.range.end.line, 0); + assert.strictEqual(variable.range.start.character, 19); + assert.strictEqual(variable.variableName, '$123'); + assert.strictEqual(variable.range.end.character, (19 + 4)); + }); + + test('Alphanumerical variables', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$normal = Get-Process +$numb3rInside = 'asdf' +$33333 = 'numbers' +$something_wrong? = 123 +4 +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 4); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$normal'; + break; + case 1: + name = '$numb3rInside'; + break; + case 2: + name = '$33333'; + break; + case 3: + name = '$something_wrong?'; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('Scoped variables', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$script:scoped = 5 +$global:scoped = 5 +$local:scoped = 5 +$using:scoped = 5 +$private:scoped = 5 +$variable:scoped = 5 +\${Script:special scoped} +$invalidscope:notdetected = 123 +4 +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 7); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$scoped'; + break; + case 1: + name = '$scoped'; + break; + case 2: + name = '$scoped'; + break; + case 3: + name = '$scoped'; + break; + case 4: + name = '$scoped'; + break; + case 5: + name = '$scoped'; + break; + case 6: + name = '$special scoped'; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('Special character variables', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +\${hello\`\`b} +\${braces} = "asdf" +\${ } = 'spaces' +\${Script:omg\`b} +\${bra%!c\\e\`} { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: `$sb = {\${hello \`{ \`} world}}`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 2); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i; + switch (i) { + case 0: + name = '$sb'; + break; + case 1: + name = '$hello { } world'; + startChar = 7; + line = 0; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('Variables used with collections', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$dict[$key] +@($element,$element2, $element3) +$normal, \${braces}, $script:scoped +@{key = $var} +@{key = \${special var}} +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 10); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$dict'; + break; + case 1: + name = '$key'; + startChar = 6; + line = 1; + break; + case 2: + name = '$element'; + startChar = 2; + line = 2; + break; + case 3: + name = '$element2'; + startChar = 11; + line = 2; + break; + case 4: + name = '$element3'; + startChar = 22; + line = 2; + break; + case 5: + name = '$normal'; + line = 3; + break; + case 6: + name = '$braces'; + startChar = 9; + line = 3; + break; + case 7: + name = '$scoped'; + startChar = 20; + line = 3; + break; + case 8: + name = '$var'; + startChar = 8; + line = 4; + break; + case 9: + name = '$special var'; + startChar = 8; + line = 5; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('Variables with modifiers', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +$a,$b +$a-$b +$a+$b +$a/$b +$a*$b +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(doc.lineCount - 1, 0, doc.lineCount - 1, 0), + frameId: 0 + }); + + assert.strictEqual(result?.length, 11); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$a'; + break; + case 1: + name = '$a'; + break; + case 2: + name = '$b'; + startChar = 3; + line = 2; + break; + case 3: + name = '$a'; + line = 3; + break; + case 4: + name = '$b'; + startChar = 3; + line = 3; + break; + case 5: + name = '$a'; + line = 4; + break; + case 6: + name = '$b'; + startChar = 3; + line = 4; + break; + case 7: + name = '$a'; + line = 5; + break; + case 8: + name = '$b'; + startChar = 3; + line = 5; + break; + case 9: + name = '$a'; + line = 6; + break; + case 10: + name = '$b'; + startChar = 3; + line = 6; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + }); + + suite('Common search range tests', async () => { + const provider: PowerShellVariableInlineValuesProvider = new PowerShellVariableInlineValuesProvider(new DocumentParser()); + + test('variables below stopped location are ignored', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +$b +$a,$b # Stopped location +$a+$b +$a/$b +$a*$b +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(3, 0, 3, 6), + frameId: 0 + }); + + assert.strictEqual(result?.length, 4); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$a'; + break; + case 1: + name = '$b'; + break; + case 2: + name = '$a'; + line = 3; + break; + case 3: + name = '$b'; + startChar = 3; + line = 3; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + test('variables in functions outside range are ignored', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { $b } +$c +function test2 { + $d +} +$e # Stopped location +$notfound +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(7, 0, 7, 3), + frameId: 0 + }); + + assert.strictEqual(result?.length, 3); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$a'; + break; + case 1: + name = '$c'; + line = 3; + break; + case 2: + name = '$e'; + line = 7; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + }); + + suite('startLocation tests', async () => { + const provider: PowerShellVariableInlineValuesProvider = new PowerShellVariableInlineValuesProvider(new DocumentParser()); + let currentSetting: string; + + suiteSetup(() => { + currentSetting = vscode.workspace.getConfiguration('powershellInlineValues').get('startLocation')!; + }); + + suiteTeardown(async () => { + await vscode.workspace.getConfiguration('powershellInlineValues').update('startLocation', currentSetting, true); + }); + + suite('startLocation is document', async () => { + suiteSetup(async () => { + await vscode.workspace.getConfiguration('powershellInlineValues').update('startLocation', 'document', true); + }); + + test('all variables from document start detected', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b + } + $a # Stopped location +} +$notfound +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(6, 4, 6, 7), + frameId: 0 + }); + + assert.strictEqual(result?.length, 2); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$a'; + break; + case 1: + name = '$a'; + startChar = 4; + line = 6; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + + }); + + suite('startLocation is currentFunction', async () => { + suiteSetup(async () => { + await vscode.workspace.getConfiguration('powershellInlineValues').update('startLocation', 'currentFunction', true); + }); + + test('only variables inside current function detected', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b + } + $a # Stopped location +} +$notfound +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(6, 4, 6, 7), + frameId: 0 + }); + + assert.strictEqual(result?.length, 1); + const variable = result![0] as vscode.InlineValueVariableLookup; + + let name: string = '$a'; + let startChar: number = 4; + let line: number = 6; + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + }); + + test('only variables inside nested function detected', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + $a + function test2 { + $b # Stopped location + } +} +$notfound +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(5, 8, 5, 11), + frameId: 0 + }); + + assert.strictEqual(result?.length, 1); + const variable = result![0] as vscode.InlineValueVariableLookup; + + let name: string = '$b'; + let startChar: number = 8; + let line: number = 5; + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + }); + + test('all variables in scope from document start detected when not in function', async () => { + const doc = await vscode.workspace.openTextDocument({ + language: 'powershell', + content: ` +$a; +function test1 { + function test2 { + $b + } + $a +} +$b # Stopped location +$notfound +`, + }); + + const result = await provider.provideInlineValues(doc, new vscode.Range(0, 0, 0, 0), { + stoppedLocation: new vscode.Range(8, 0, 8, 3), + frameId: 0 + }); + + assert.strictEqual(result?.length, 2); + for (let i = 0; i < result.length; i++) { + const variable = result![i] as vscode.InlineValueVariableLookup; + let name: string = ''; + let startChar: number = 0; + let line: number = i + 1; + switch (i) { + case 0: + name = '$a'; + break; + case 1: + name = '$b'; + startChar = 0; + line = 8; + break; + } + + assert.strictEqual(variable.caseSensitiveLookup, false); + assert.strictEqual(variable.range.start.line, line); + assert.strictEqual(variable.range.end.line, line); + assert.strictEqual(variable.range.start.character, startChar); + assert.strictEqual(variable.variableName, name); + assert.strictEqual(variable.range.end.character, name.length + startChar); + } + }); + }); + }); +}); diff --git a/src/test/suite/utils.test.ts b/src/test/suite/utils.test.ts new file mode 100644 index 0000000..313036e --- /dev/null +++ b/src/test/suite/utils.test.ts @@ -0,0 +1,34 @@ +import * as assert from 'assert'; +import * as utils from "../../utils"; +import { DocumentSymbol, Range, SymbolKind } from 'vscode'; + +suite('Util tests', async () => { + suite('range() function', async () => { + test('returns array of 1,2,3 when called with 1,3', async () => { + const result = utils.range(1, 3); + assert.strictEqual(result.length, 3); + assert.strictEqual(result[0], 1); + assert.strictEqual(result[1], 2); + assert.strictEqual(result[2], 3); + }); + }); + + suite('flattenSymbols() function', async () => { + test('returns flat array of symbols with child-symbols', async () => { + const root = new DocumentSymbol('f1', '', SymbolKind.Function, new Range(0, 0, 0, 0), new Range(0, 0, 0, 0)); + const child1 = new DocumentSymbol('f1-child1', '', SymbolKind.Function, new Range(0, 0, 0, 0), new Range(0, 0, 0, 0)); + const child2 = new DocumentSymbol('f1-child2', '', SymbolKind.Function, new Range(0, 0, 0, 0), new Range(0, 0, 0, 0)); + root.children = [child1, child2]; + child1.children = [new DocumentSymbol('f1-child1-inner1', '', SymbolKind.Function, new Range(0, 0, 0, 0), new Range(0, 0, 0, 0))]; + const root2 = new DocumentSymbol('f2', '', SymbolKind.Function, new Range(0, 0, 0, 0), new Range(0, 0, 0, 0)); + + const res = utils.flattenSymbols([root, root2]); + assert.strictEqual(res.length, 5); + assert.strictEqual(res[0].name, 'f1'); + assert.strictEqual(res[1].name, 'f1-child1'); + assert.strictEqual(res[2].name, 'f1-child1-inner1'); + assert.strictEqual(res[3].name, 'f1-child2'); + assert.strictEqual(res[4].name, 'f2'); + }); + }); +}); diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts new file mode 100644 index 0000000..0801f66 --- /dev/null +++ b/src/test/testUtils.ts @@ -0,0 +1,43 @@ +import * as vscode from "vscode"; + +const extensionId = 'TylerLeonhardt.vscode-inline-values-powershell'; + +/** + * IExternalPowerShellDetails and IPowerShellExtensionClient + * https://github.com/PowerShell/vscode-powershell/blob/c323846803f0d43f75562a7fd1e8c225099cc528/src/features/ExternalApi.ts#L10-L22 + */ +export interface IExternalPowerShellDetails { + exePath: string; + version: string; + displayName: string; + architecture: string; +} + +export interface IPowerShellExtensionClient { + registerExternalExtension(id: string, apiVersion?: string): string; + unregisterExternalExtension(uuid: string): boolean; + getPowerShellVersionDetails(uuid: string): Promise; + waitUntilStarted(uuid: string): Promise; +} + +/** + * Modified version of ensureEditorServicesIsConnected() + * https://github.com/PowerShell/vscode-powershell/blob/c323846803f0d43f75562a7fd1e8c225099cc528/test/utils.ts#L17-L21 + */ +export async function ensureExtensionIsActivated(): Promise> { + const powershellExtension = vscode.extensions.getExtension("ms-vscode.PowerShell") || vscode.extensions.getExtension("ms-vscode.PowerShell-Preview"); + if (!powershellExtension!.isActive) { await powershellExtension!.activate(); } + return powershellExtension!; +} + +/** + * ensureEditorServicesIsConnected() + * https://github.com/PowerShell/vscode-powershell/blob/c323846803f0d43f75562a7fd1e8c225099cc528/test/utils.ts#L23-L29 + */ +export async function ensureEditorServicesIsConnected(): Promise { + const powershellExtension = await ensureExtensionIsActivated(); + const client = powershellExtension!.exports as IPowerShellExtensionClient; + const sessionId = client.registerExternalExtension(extensionId); + await client.waitUntilStarted(sessionId); + client.unregisterExternalExtension(sessionId); +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..593f75f --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,18 @@ +import { DocumentSymbol } from 'vscode'; + +// Because 1..10 isn't supported here... +export function range(start: number, end: number) { + return Array(end - start + 1).fill(undefined).map((_, i) => start + i); +} + +// Convert the symbol-tree to a flat array +export function flattenSymbols(symbols: DocumentSymbol[]): DocumentSymbol[] { + let result: DocumentSymbol[] = []; + symbols.map(symbol => { + result.push(symbol); + if (symbol.children && symbol.children.length > 0) { + result = result.concat(flattenSymbols(symbol.children)); + } + }); + return result; +}