From 04f98c45bdff58a33e83e30998a51e4914b3cb83 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Fri, 15 Mar 2019 14:58:25 -0400 Subject: [PATCH 01/17] added configuration section to contributes --- package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/package.json b/package.json index f7ab994..16aaa90 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,18 @@ "when": "editorTextFocus && editorLangId == 'azcli'" } ], + "configuration": { + "type": "object", + "title": "Azure CLI Tools Configuration", + "properties": { + "ms-azurecli.showResponseInDifferentTab": { + "type": "boolean", + "default": false, + "scope": "resource", + "description": "Show response in different tab" + } + } + }, "menus": { "editor/context": [ { From bab48c5ff52f509b3f51c035357d3c19a69ff59c Mon Sep 17 00:00:00 2001 From: mburleigh Date: Fri, 15 Mar 2019 14:59:32 -0400 Subject: [PATCH 02/17] added file to support extension specific config --- src/configurationSettings.ts | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/configurationSettings.ts diff --git a/src/configurationSettings.ts b/src/configurationSettings.ts new file mode 100644 index 0000000..3704ac6 --- /dev/null +++ b/src/configurationSettings.ts @@ -0,0 +1,50 @@ +import { Event, EventEmitter, window, workspace } from 'vscode'; + +export interface IAzureCliToolsSettings { + showResponseInDifferentTab: boolean; +} + +export class AzureCliToolsSettings implements IAzureCliToolsSettings { + public showResponseInDifferentTab: boolean = false; + + private static _instance: AzureCliToolsSettings; + + public static get Instance(): AzureCliToolsSettings { + if (!AzureCliToolsSettings._instance) { + AzureCliToolsSettings._instance = new AzureCliToolsSettings(); + } + + return AzureCliToolsSettings._instance; + } + + public readonly configurationUpdateEventEmitter = new EventEmitter(); + + public get onDidChangeConfiguration(): Event { + return this.configurationUpdateEventEmitter.event; + } + + private constructor() { + workspace.onDidChangeConfiguration(() => { + this.initializeSettings(); + this.configurationUpdateEventEmitter.fire(); + }); + window.onDidChangeActiveTextEditor(e => { + if (e) { + this.initializeSettings(); + this.configurationUpdateEventEmitter.fire(); + } + }); + + this.initializeSettings(); + } + + private initializeSettings() { + const editor = window.activeTextEditor; + const document = editor && editor.document; + + const azureCliToolsSettings = workspace.getConfiguration("ms-azurecli", document ? document.uri : null); + + this.showResponseInDifferentTab = azureCliToolsSettings.get("showResponseInDifferentTab", false); + } + +} \ No newline at end of file From eb0194140de9653cfce557871a9ca654bf6ab031 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Fri, 15 Mar 2019 15:00:40 -0400 Subject: [PATCH 03/17] support for showResponseInDifferentTab config setting --- src/extension.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4e526d7..51e74ec 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -9,6 +9,7 @@ import { HoverProvider, Hover, SnippetString, StatusBarAlignment, StatusBarItem, import { AzService, CompletionKind, Arguments, Status } from './azService'; import { parse, findNode } from './parser'; import { exec } from './utils'; +import { AzureCliToolsSettings } from './configurationSettings'; export function activate(context: ExtensionContext) { const azService = new AzService(azNotFound); @@ -154,6 +155,7 @@ class RunLineInEditor { private queryEnabled = false; private query: string | undefined; private disposables: Disposable[] = []; + private readonly settings: AzureCliToolsSettings = AzureCliToolsSettings.Instance; constructor(private status: StatusBarInfo) { this.disposables.push(commands.registerTextEditorCommand('ms-azurecli.toggleLiveQuery', editor => this.toggleQuery(editor))); @@ -167,7 +169,8 @@ class RunLineInEditor { this.query = undefined; // TODO const cursor = source.selection.active; const line = source.document.lineAt(cursor).text; - return this.findResultDocument() + const isText = (line.indexOf('--query') != -1) || (line.indexOf('-h') != -1) || (line.indexOf('--help') != -1); + return this.findResultDocument(isText) .then(document => window.showTextDocument(document, ViewColumn.Two, true)) .then(target => replaceContent(target, JSON.stringify({ 'Running command': line }) + '\n') .then(() => exec(line)) @@ -187,7 +190,17 @@ class RunLineInEditor { this.updateResult(); } - private findResultDocument() { + private findResultDocument(isText: boolean = false) { + if (this.settings.showResponseInDifferentTab) { + if (isText) { + return workspace.openTextDocument({ language: 'text' }) + .then(document => this.resultDocument = document); + } + else { + return workspace.openTextDocument({ language: 'json' }) + .then(document => this.resultDocument = document); + } + } if (this.resultDocument) { return Promise.resolve(this.resultDocument); } From afffc82a89c20d72ce88f811c5b12c8a9418b47e Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 20 Mar 2019 11:22:00 -0400 Subject: [PATCH 04/17] add elegant-spinner --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 16aaa90..1ad412c 100644 --- a/package.json +++ b/package.json @@ -142,7 +142,8 @@ "@types/semver": "5.5.0", "tslint": "5.14.0", "typescript": "3.3.3333", - "vscode": "1.1.30" + "vscode": "1.1.30", + "elegant-spinner": "1.0.1" }, "dependencies": { "jmespath": "0.15.0", From afe9c399ef681e541381a0b64cac89f6fd7cd1e4 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 20 Mar 2019 11:24:26 -0400 Subject: [PATCH 05/17] refactor handling for document language add status bar item to RunLineInEditor class add execution timing to RunLineInEditor class --- src/extension.ts | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 51e74ec..6f2a44c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as jmespath from 'jmespath'; - import { HoverProvider, Hover, SnippetString, StatusBarAlignment, StatusBarItem, ExtensionContext, TextDocument, TextDocumentChangeEvent, Disposable, TextEditor, Selection, languages, commands, Range, ViewColumn, Position, CancellationToken, ProviderResult, CompletionItem, CompletionList, CompletionItemKind, CompletionItemProvider, window, workspace, env, Uri } from 'vscode'; import { AzService, CompletionKind, Arguments, Status } from './azService'; @@ -20,7 +19,6 @@ export function activate(context: ExtensionContext) { context.subscriptions.push(new RunLineInTerminal()); context.subscriptions.push(new RunLineInEditor(status)); context.subscriptions.push(commands.registerCommand('ms-azurecli.installAzureCLI', installAzureCLI)); - } const completionKinds: Record = { @@ -148,6 +146,8 @@ class RunLineInTerminal { } } +const elegantSpinner = require('elegant-spinner'); + class RunLineInEditor { private resultDocument: TextDocument | undefined; @@ -156,29 +156,43 @@ class RunLineInEditor { private query: string | undefined; private disposables: Disposable[] = []; private readonly settings: AzureCliToolsSettings = AzureCliToolsSettings.Instance; + private runStatusBarItem: StatusBarItem; + private interval!: NodeJS.Timer; + private spinner = elegantSpinner(); constructor(private status: StatusBarInfo) { this.disposables.push(commands.registerTextEditorCommand('ms-azurecli.toggleLiveQuery', editor => this.toggleQuery(editor))); this.disposables.push(commands.registerTextEditorCommand('ms-azurecli.runLineInEditor', editor => this.run(editor))); this.disposables.push(workspace.onDidCloseTextDocument(document => this.close(document))); this.disposables.push(workspace.onDidChangeTextDocument(event => this.change(event))); + + this.runStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); } private run(source: TextEditor) { + var t0 = Date.now(); + this.interval = setInterval(() => { + this.runStatusBarItem.text = `Waiting for response ${this.spinner()}`; + }, 50); + this.runStatusBarItem.show(); + this.parsedResult = undefined; this.query = undefined; // TODO const cursor = source.selection.active; const line = source.document.lineAt(cursor).text; - const isText = (line.indexOf('--query') != -1) || (line.indexOf('-h') != -1) || (line.indexOf('--help') != -1); - return this.findResultDocument(isText) + const isPlainText = (line.indexOf('--query') !== -1) || (line.indexOf('-h') !== -1) || (line.indexOf('--help') !== -1); + return this.findResultDocument() .then(document => window.showTextDocument(document, ViewColumn.Two, true)) .then(target => replaceContent(target, JSON.stringify({ 'Running command': line }) + '\n') .then(() => exec(line)) .then(({ stdout }) => stdout, ({ stdout, stderr }) => JSON.stringify({ stderr, stdout }, null, ' ')) - .then(content => replaceContent(target, content) - .then(() => this.parsedResult = JSON.parse(content)) - .then(undefined, err => {}) - ) + .then(content => { + replaceContent(target, content, isPlainText ? 'plaintext' : '') + .then(() => this.parsedResult = JSON.parse(content)) + .then(undefined, err => {}); + clearInterval(this.interval); + this.runStatusBarItem.text = 'AZ CLI command executed in ' + (Date.now() - t0) + ' milliseconds.'; + }) ) .then(undefined, console.error); } @@ -190,16 +204,10 @@ class RunLineInEditor { this.updateResult(); } - private findResultDocument(isText: boolean = false) { + private findResultDocument() { if (this.settings.showResponseInDifferentTab) { - if (isText) { - return workspace.openTextDocument({ language: 'text' }) - .then(document => this.resultDocument = document); - } - else { - return workspace.openTextDocument({ language: 'json' }) - .then(document => this.resultDocument = document); - } + return workspace.openTextDocument({ language: 'json' }) + .then(document => this.resultDocument = document); } if (this.resultDocument) { return Promise.resolve(this.resultDocument); @@ -255,6 +263,7 @@ class RunLineInEditor { dispose() { this.disposables.forEach(disposable => disposable.dispose()); + this.runStatusBarItem.dispose(); } } @@ -317,8 +326,11 @@ function allMatches(regex: RegExp, string: string, group: number) { } } -function replaceContent(editor: TextEditor, content: string) { +function replaceContent(editor: TextEditor, content: string, documentLanguage: string = '') { const document = editor.document; + if (documentLanguage) { + languages.setTextDocumentLanguage(document, documentLanguage); + } const all = new Range(new Position(0, 0), document.lineAt(document.lineCount - 1).range.end); return editor.edit(builder => builder.replace(all, content)) .then(() => editor.selections = [new Selection(0, 0, 0, 0)]); From e6eddd0e7d231c4f7aec238381865ae7249c1533 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Tue, 6 Aug 2019 14:42:37 -0400 Subject: [PATCH 06/17] add support for multiline commands add support for executing selections fix some === warnings rename 'line' to 'command' for clarity --- src/extension.ts | 154 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 117 insertions(+), 37 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index efe00e9..3af82dc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -171,41 +171,124 @@ class RunLineInEditor { private runningCommandCount : number = 0; private run(source: TextEditor) { - this.runningCommandCount += 1; - const t0 = Date.now(); - if (this.runningCommandCount == 1) + const command = this.GetSelectedCommand(source); + if (command.length > 0) { - this.statusBarItemText = `Azure CLI: Waiting for response`; - this.statusBarUpdateInterval = setInterval(() => { - if (this.runningCommandCount == 1) - { - this.commandRunningStatusBarItem.text = `${this.statusBarItemText} ${this.statusBarSpinner()}`; - } - else - { - this.commandRunningStatusBarItem.text = `${this.statusBarItemText} [${this.runningCommandCount}] ${this.statusBarSpinner()}`; + this.runningCommandCount += 1; + const t0 = Date.now(); + if (this.runningCommandCount === 1) + { + this.statusBarItemText = `Azure CLI: Waiting for response`; + this.statusBarUpdateInterval = setInterval(() => { + if (this.runningCommandCount === 1) + { + this.commandRunningStatusBarItem.text = `${this.statusBarItemText} ${this.statusBarSpinner()}`; + } + else + { + this.commandRunningStatusBarItem.text = `${this.statusBarItemText} [${this.runningCommandCount}] ${this.statusBarSpinner()}`; + } + }, 50); + } + this.commandRunningStatusBarItem.show(); + clearTimeout(this.hideStatusBarItemTimeout); + + this.parsedResult = undefined; + this.query = undefined; // TODO + return this.findResultDocument() + .then(document => window.showTextDocument(document, ViewColumn.Two, true)) + .then(target => replaceContent(target, JSON.stringify({ 'Running command': command }) + '\n') + .then(() => exec(command)) + .then(({ stdout }) => stdout, ({ stdout, stderr }) => JSON.stringify({ stderr, stdout }, null, ' ')) + .then(content => replaceContent(target, content) + .then(() => this.parsedResult = JSON.parse(content)) + .then(undefined, err => {}) + ) + .then(() => this.commandFinished(t0)) + ) + .then(undefined, console.error); + } + } + + private GetSelectedCommand(source: TextEditor) + { + if (source.selection.isEmpty) + { + //const cursor = source.selection.active; + //var lineNumber = source.document.lineAt(source.selection.active).lineNumber; + + var lineNumber = source.selection.active.line; + if (source.document.lineAt(lineNumber).text.length === 0) + { + window.showInformationMessage("Please put the cursor on a line that contains a command."); + return ""; + } + + // find the start of the command (if necessary) + while(!source.document.lineAt(lineNumber).text.trim().toLowerCase().startsWith("az")) + { + lineNumber--; + } + + var command = this.StripComments(source.document.lineAt(lineNumber).text); + + // using backtick (`) as continuation character + while (command.trim().endsWith("`")) + { + // concatenate all lines into a single command + lineNumber ++; + command = command.replace("`", "") + this.StripComments(source.document.lineAt(lineNumber).text); + } + return command; + } + else + { + // execute only the selected text + const selectionStart = source.selection.start; + const selectionEnd = source.selection.end; + if (selectionStart.line === selectionEnd.line) + { + return this.StripComments(source.document.getText(new Range(selectionStart, selectionEnd))); + } + else + { + command = this.StripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); + for (let index = selectionStart.line+1; index <= selectionEnd.line; index++) { + var line = this.StripComments(source.document.lineAt(index).text); + if (line.startsWith("az")) + { + window.showErrorMessage("Multiple command selection not supported"); + return ""; + } + if (index === selectionEnd.line) + { + command = command.replace("`", "") + line.substring(0, selectionEnd.character); + } + else + { + command = command.replace("`", "") + line; + } } - }, 50); + return command; + } } - this.commandRunningStatusBarItem.show(); - clearTimeout(this.hideStatusBarItemTimeout); - - this.parsedResult = undefined; - this.query = undefined; // TODO - const cursor = source.selection.active; - const line = source.document.lineAt(cursor).text; - return this.findResultDocument() - .then(document => window.showTextDocument(document, ViewColumn.Two, true)) - .then(target => replaceContent(target, JSON.stringify({ 'Running command': line }) + '\n') - .then(() => exec(line)) - .then(({ stdout }) => stdout, ({ stdout, stderr }) => JSON.stringify({ stderr, stdout }, null, ' ')) - .then(content => replaceContent(target, content) - .then(() => this.parsedResult = JSON.parse(content)) - .then(undefined, err => {}) - ) - .then(() => this.commandFinished(t0)) - ) - .then(undefined, console.error); + } + + private StripComments(text: string) + { + // allow for single line comments on the same line as the command (// or #) + var i = text.search("//"); + if (i !== -1) + { + return text.substring(0, i) + } + i = text.search("#"); + if (i !== -1) + { + return text.substring(0, i) + } + + return text; } private commandFinished(startTime: number) @@ -214,7 +297,7 @@ class RunLineInEditor { this.statusBarItemText = 'Azure CLI: Executed in ' + (Date.now() - startTime) + ' milliseconds'; this.commandRunningStatusBarItem.text = this.statusBarItemText; - if (this.runningCommandCount == 0) + if (this.runningCommandCount === 0) { clearInterval(this.statusBarUpdateInterval); @@ -348,11 +431,8 @@ function allMatches(regex: RegExp, string: string, group: number) { } } -function replaceContent(editor: TextEditor, content: string, documentLanguage: string = '') { +function replaceContent(editor: TextEditor, content: string) { const document = editor.document; - if (documentLanguage) { - languages.setTextDocumentLanguage(document, documentLanguage); - } const all = new Range(new Position(0, 0), document.lineAt(document.lineCount - 1).range.end); const edit = new WorkspaceEdit(); edit.replace(document.uri, all, content); From c199230b01a9757a0f5ea30c0cd8f8df742df80d Mon Sep 17 00:00:00 2001 From: Matthew Burleigh Date: Tue, 24 Sep 2019 08:29:56 -0400 Subject: [PATCH 07/17] Delete configurationSettings.ts --- src/configurationSettings.ts | 50 ------------------------------------ 1 file changed, 50 deletions(-) delete mode 100644 src/configurationSettings.ts diff --git a/src/configurationSettings.ts b/src/configurationSettings.ts deleted file mode 100644 index 3704ac6..0000000 --- a/src/configurationSettings.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Event, EventEmitter, window, workspace } from 'vscode'; - -export interface IAzureCliToolsSettings { - showResponseInDifferentTab: boolean; -} - -export class AzureCliToolsSettings implements IAzureCliToolsSettings { - public showResponseInDifferentTab: boolean = false; - - private static _instance: AzureCliToolsSettings; - - public static get Instance(): AzureCliToolsSettings { - if (!AzureCliToolsSettings._instance) { - AzureCliToolsSettings._instance = new AzureCliToolsSettings(); - } - - return AzureCliToolsSettings._instance; - } - - public readonly configurationUpdateEventEmitter = new EventEmitter(); - - public get onDidChangeConfiguration(): Event { - return this.configurationUpdateEventEmitter.event; - } - - private constructor() { - workspace.onDidChangeConfiguration(() => { - this.initializeSettings(); - this.configurationUpdateEventEmitter.fire(); - }); - window.onDidChangeActiveTextEditor(e => { - if (e) { - this.initializeSettings(); - this.configurationUpdateEventEmitter.fire(); - } - }); - - this.initializeSettings(); - } - - private initializeSettings() { - const editor = window.activeTextEditor; - const document = editor && editor.document; - - const azureCliToolsSettings = workspace.getConfiguration("ms-azurecli", document ? document.uri : null); - - this.showResponseInDifferentTab = azureCliToolsSettings.get("showResponseInDifferentTab", false); - } - -} \ No newline at end of file From 38a650e7a4ecb15bc09fbbdc83c2e933b65f8b14 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 11 Dec 2019 09:14:38 -0500 Subject: [PATCH 08/17] move { from separate line --- src/extension.ts | 60 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 5d0d5d6..a9b68f2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -172,20 +172,16 @@ class RunLineInEditor { private runningCommandCount : number = 0; private run(source: TextEditor) { const command = this.GetSelectedCommand(source); - if (command.length > 0) - { + if (command.length > 0) { this.runningCommandCount += 1; const t0 = Date.now(); - if (this.runningCommandCount === 1) - { + if (this.runningCommandCount === 1) { this.statusBarItemText = `Azure CLI: Waiting for response`; this.statusBarUpdateInterval = setInterval(() => { - if (this.runningCommandCount === 1) - { + if (this.runningCommandCount === 1) { this.commandRunningStatusBarItem.text = `${this.statusBarItemText} ${this.statusBarSpinner()}`; } - else - { + else { this.commandRunningStatusBarItem.text = `${this.statusBarItemText} [${this.runningCommandCount}] ${this.statusBarSpinner()}`; } }, 50); @@ -210,59 +206,48 @@ class RunLineInEditor { } } - private GetSelectedCommand(source: TextEditor) - { - if (source.selection.isEmpty) - { + private GetSelectedCommand(source: TextEditor) { + if (source.selection.isEmpty) { var lineNumber = source.selection.active.line; - if (source.document.lineAt(lineNumber).text.length === 0) - { + if (source.document.lineAt(lineNumber).text.length === 0) { window.showInformationMessage("Please put the cursor on a line that contains a command."); return ""; } // find the start of the command (if necessary) - while(!source.document.lineAt(lineNumber).text.trim().toLowerCase().startsWith("az")) - { + while(!source.document.lineAt(lineNumber).text.trim().toLowerCase().startsWith("az")) { lineNumber--; } var command = this.StripComments(source.document.lineAt(lineNumber).text); // using backtick (`) as continuation character - while (command.trim().endsWith("`")) - { + while (command.trim().endsWith("`")) { // concatenate all lines into a single command lineNumber ++; command = command.replace("`", "") + this.StripComments(source.document.lineAt(lineNumber).text); } return command; } - else - { + else { // execute only the selected text const selectionStart = source.selection.start; const selectionEnd = source.selection.end; - if (selectionStart.line === selectionEnd.line) - { + if (selectionStart.line === selectionEnd.line) { return this.StripComments(source.document.getText(new Range(selectionStart, selectionEnd))); } - else - { + else { command = this.StripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); for (let index = selectionStart.line+1; index <= selectionEnd.line; index++) { var line = this.StripComments(source.document.lineAt(index).text); - if (line.startsWith("az")) - { + if (line.startsWith("az")) { window.showErrorMessage("Multiple command selection not supported"); return ""; } - if (index === selectionEnd.line) - { + if (index === selectionEnd.line) { command = command.replace("`", "") + line.substring(0, selectionEnd.character); } - else - { + else { command = command.replace("`", "") + line; } } @@ -271,31 +256,26 @@ class RunLineInEditor { } } - private StripComments(text: string) - { + private StripComments(text: string) { // allow for single line comments on the same line as the command (// or #) var i = text.search("//"); - if (i !== -1) - { + if (i !== -1) { return text.substring(0, i) } i = text.search("#"); - if (i !== -1) - { + if (i !== -1) { return text.substring(0, i) } return text; } - private commandFinished(startTime: number) - { + private commandFinished(startTime: number) { this.runningCommandCount -= 1 this.statusBarItemText = 'Azure CLI: Executed in ' + (Date.now() - startTime) + ' milliseconds'; this.commandRunningStatusBarItem.text = this.statusBarItemText; - if (this.runningCommandCount === 0) - { + if (this.runningCommandCount === 0) { clearInterval(this.statusBarUpdateInterval); // hide status bar item after 10 seconds to keep status bar uncluttered From 57ee605c80b55381bd5b0a37bf107bb3c12aa3cf Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 11 Dec 2019 09:36:22 -0500 Subject: [PATCH 09/17] lowercase first char of function names --- src/extension.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index a9b68f2..17572e2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -171,7 +171,7 @@ class RunLineInEditor { private runningCommandCount : number = 0; private run(source: TextEditor) { - const command = this.GetSelectedCommand(source); + const command = this.getSelectedCommand(source); if (command.length > 0) { this.runningCommandCount += 1; const t0 = Date.now(); @@ -206,7 +206,7 @@ class RunLineInEditor { } } - private GetSelectedCommand(source: TextEditor) { + private getSelectedCommand(source: TextEditor) { if (source.selection.isEmpty) { var lineNumber = source.selection.active.line; if (source.document.lineAt(lineNumber).text.length === 0) { @@ -219,13 +219,13 @@ class RunLineInEditor { lineNumber--; } - var command = this.StripComments(source.document.lineAt(lineNumber).text); + var command = this.stripComments(source.document.lineAt(lineNumber).text); // using backtick (`) as continuation character while (command.trim().endsWith("`")) { // concatenate all lines into a single command lineNumber ++; - command = command.replace("`", "") + this.StripComments(source.document.lineAt(lineNumber).text); + command = command.replace("`", "") + this.stripComments(source.document.lineAt(lineNumber).text); } return command; } @@ -234,12 +234,12 @@ class RunLineInEditor { const selectionStart = source.selection.start; const selectionEnd = source.selection.end; if (selectionStart.line === selectionEnd.line) { - return this.StripComments(source.document.getText(new Range(selectionStart, selectionEnd))); + return this.stripComments(source.document.getText(new Range(selectionStart, selectionEnd))); } else { - command = this.StripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); + command = this.stripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); for (let index = selectionStart.line+1; index <= selectionEnd.line; index++) { - var line = this.StripComments(source.document.lineAt(index).text); + var line = this.stripComments(source.document.lineAt(index).text); if (line.startsWith("az")) { window.showErrorMessage("Multiple command selection not supported"); return ""; @@ -256,7 +256,7 @@ class RunLineInEditor { } } - private StripComments(text: string) { + private stripComments(text: string) { // allow for single line comments on the same line as the command (// or #) var i = text.search("//"); if (i !== -1) { From 98ff486a87776762f492757f49150fc4fb43e23e Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 11 Dec 2019 15:03:17 -0500 Subject: [PATCH 10/17] refactor getSelectedCommand() * add comments * assign strings to variables * use slice() to remove continuation character --- src/extension.ts | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 17572e2..96eff28 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -207,25 +207,29 @@ class RunLineInEditor { } private getSelectedCommand(source: TextEditor) { + const continuationCharacter = "`"; + const commandPrefix = "az"; + if (source.selection.isEmpty) { var lineNumber = source.selection.active.line; if (source.document.lineAt(lineNumber).text.length === 0) { - window.showInformationMessage("Please put the cursor on a line that contains a command."); + window.showInformationMessage("Please put the cursor on a line that contains a command (or part of a command)."); return ""; } - // find the start of the command (if necessary) - while(!source.document.lineAt(lineNumber).text.trim().toLowerCase().startsWith("az")) { + // look upwards find the start of the command (if necessary) + while(!source.document.lineAt(lineNumber).text.trim().toLowerCase().startsWith(commandPrefix)) { lineNumber--; } + // this will be the first (maybe only) line of the command var command = this.stripComments(source.document.lineAt(lineNumber).text); // using backtick (`) as continuation character - while (command.trim().endsWith("`")) { + while (command.trim().endsWith(continuationCharacter)) { // concatenate all lines into a single command lineNumber ++; - command = command.replace("`", "") + this.stripComments(source.document.lineAt(lineNumber).text); + command = command.trim().slice(0, -1) + this.stripComments(source.document.lineAt(lineNumber).text); } return command; } @@ -234,21 +238,30 @@ class RunLineInEditor { const selectionStart = source.selection.start; const selectionEnd = source.selection.end; if (selectionStart.line === selectionEnd.line) { + // single line command return this.stripComments(source.document.getText(new Range(selectionStart, selectionEnd))); } else { + // multiline command command = this.stripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); for (let index = selectionStart.line+1; index <= selectionEnd.line; index++) { + if (command.trim().endsWith(continuationCharacter)) { + command = command.trim().slice(0, -1); // remove continuation character from command + } + var line = this.stripComments(source.document.lineAt(index).text); - if (line.startsWith("az")) { + + if (line.trim().toLowerCase().startsWith(commandPrefix)) { window.showErrorMessage("Multiple command selection not supported"); return ""; } + + // append this line to the command string if (index === selectionEnd.line) { - command = command.replace("`", "") + line.substring(0, selectionEnd.character); + command = command + line.substring(0, selectionEnd.character); // only append up to the end of the selection } else { - command = command.replace("`", "") + line; + command = command + line; } } return command; From 8a70057d479c7e9eec7427d1cf71da0a7f96f933 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Wed, 11 Dec 2019 15:23:26 -0500 Subject: [PATCH 11/17] move comment to declaration --- src/extension.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 96eff28..2390d7a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -207,7 +207,7 @@ class RunLineInEditor { } private getSelectedCommand(source: TextEditor) { - const continuationCharacter = "`"; + const continuationCharacter = "`"; // using backtick (`) as continuation character const commandPrefix = "az"; if (source.selection.isEmpty) { @@ -225,7 +225,6 @@ class RunLineInEditor { // this will be the first (maybe only) line of the command var command = this.stripComments(source.document.lineAt(lineNumber).text); - // using backtick (`) as continuation character while (command.trim().endsWith(continuationCharacter)) { // concatenate all lines into a single command lineNumber ++; From 0f0b26f96d31e0888c6d315e2d4e9f36c47aee61 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Thu, 12 Dec 2019 09:04:19 -0500 Subject: [PATCH 12/17] change stripComments(): * remove support for // comments * support full line comments --- src/extension.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 2390d7a..c3212af 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -206,8 +206,9 @@ class RunLineInEditor { } } + private continuationCharacter : string = "`"; // using backtick (`) as continuation character + private getSelectedCommand(source: TextEditor) { - const continuationCharacter = "`"; // using backtick (`) as continuation character const commandPrefix = "az"; if (source.selection.isEmpty) { @@ -225,7 +226,7 @@ class RunLineInEditor { // this will be the first (maybe only) line of the command var command = this.stripComments(source.document.lineAt(lineNumber).text); - while (command.trim().endsWith(continuationCharacter)) { + while (command.trim().endsWith(this.continuationCharacter)) { // concatenate all lines into a single command lineNumber ++; command = command.trim().slice(0, -1) + this.stripComments(source.document.lineAt(lineNumber).text); @@ -244,7 +245,7 @@ class RunLineInEditor { // multiline command command = this.stripComments(source.document.lineAt(selectionStart.line).text.substring(selectionStart.character)); for (let index = selectionStart.line+1; index <= selectionEnd.line; index++) { - if (command.trim().endsWith(continuationCharacter)) { + if (command.trim().endsWith(this.continuationCharacter)) { command = command.trim().slice(0, -1); // remove continuation character from command } @@ -269,16 +270,22 @@ class RunLineInEditor { } private stripComments(text: string) { - // allow for single line comments on the same line as the command (// or #) - var i = text.search("//"); - if (i !== -1) { - return text.substring(0, i) + // allow for single line comments (whole line or on the same line as the command) + // var i = text.search("//"); + // if (i !== -1) { + // return text.substring(0, i) + // } + + if (text.trim().startsWith("#")) { + return this.continuationCharacter; // don't let a comment terminate a sequence of command fragments } - i = text.search("#"); + + var i = text.search("#"); if (i !== -1) { return text.substring(0, i) } + // default is no comments found return text; } From 2928e94182425bfba18b7738b91870bcb9dd3f95 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Thu, 12 Dec 2019 09:40:44 -0500 Subject: [PATCH 13/17] allow for "#" character in JMESPath queries without being a comment --- src/extension.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index c3212af..98a651b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -280,11 +280,16 @@ class RunLineInEditor { return this.continuationCharacter; // don't let a comment terminate a sequence of command fragments } - var i = text.search("#"); + var i = text.search(" #"); if (i !== -1) { return text.substring(0, i) } + i = text.search(this.continuationCharacter + "#"); + if (i !== -1) { + return text.substring(0, i+1) + } + // default is no comments found return text; } From 981c0013f6b891babd0eeb544dad0f7d32ab86b7 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Fri, 20 Dec 2019 11:16:00 -0500 Subject: [PATCH 14/17] add setting for user specified configuration character --- package.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package.json b/package.json index a933f68..70e969f 100644 --- a/package.json +++ b/package.json @@ -98,6 +98,12 @@ "default": false, "scope": "resource", "description": "Controls whether showing the result from running an Azure CLI command in an editor should always create a new editor." + }, + "azureCLI.continuationCharacter": { + "type": "string", + "default": "", + "scope": "resource", + "description": "Override the default continuation character (backtick [`] on Windows otherwise backslash [\\]) used for multiline commands" } } }, From ff0226282d2c71257d2ba58ce046b20b05e237a1 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Fri, 20 Dec 2019 11:18:27 -0500 Subject: [PATCH 15/17] default continuation character based on OS platform allow for user specified continuation character --- src/extension.ts | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 98a651b..8af8780 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as jmespath from 'jmespath'; -import { HoverProvider, Hover, SnippetString, StatusBarAlignment, StatusBarItem, ExtensionContext, TextDocument, TextDocumentChangeEvent, Disposable, TextEditor, Selection, languages, commands, Range, ViewColumn, Position, CancellationToken, ProviderResult, CompletionItem, CompletionList, CompletionItemKind, CompletionItemProvider, window, workspace, env, Uri, WorkspaceEdit } from 'vscode'; +import { HoverProvider, Hover, SnippetString, StatusBarAlignment, StatusBarItem, ExtensionContext, TextDocument, TextDocumentChangeEvent, Disposable, TextEditor, Selection, languages, commands, Range, ViewColumn, Position, CancellationToken, ProviderResult, CompletionItem, CompletionList, CompletionItemKind, CompletionItemProvider, window, workspace, env, Uri, WorkspaceEdit, } from 'vscode'; +import * as process from "process"; import { AzService, CompletionKind, Arguments, Status } from './azService'; import { parse, findNode } from './parser'; @@ -158,6 +159,8 @@ class RunLineInEditor { private statusBarSpinner = spinner(); private hideStatusBarItemTimeout! : NodeJS.Timeout; private statusBarItemText : string = ''; + // using backtick (`) as continuation character on Windows, backslash (\) on other systems + private continuationCharacter : string = process.platform === "win32" ? "`" : "\\"; constructor(private status: StatusBarInfo) { this.disposables.push(commands.registerTextEditorCommand('ms-azurecli.toggleLiveQuery', editor => this.toggleQuery(editor))); @@ -166,11 +169,12 @@ class RunLineInEditor { this.disposables.push(workspace.onDidChangeTextDocument(event => this.change(event))); this.commandRunningStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); - this.disposables.push(this.commandRunningStatusBarItem); + this.disposables.push(this.commandRunningStatusBarItem); } private runningCommandCount : number = 0; private run(source: TextEditor) { + this.RefreshContinuationCharacter(); const command = this.getSelectedCommand(source); if (command.length > 0) { this.runningCommandCount += 1; @@ -206,7 +210,16 @@ class RunLineInEditor { } } - private continuationCharacter : string = "`"; // using backtick (`) as continuation character + private RefreshContinuationCharacter() { + // the continuation character setting can be changed after the extension is loaded + const settingsContinuationCharacter = workspace.getConfiguration('azureCLI', null).get('continuationCharacter', ""); + if (settingsContinuationCharacter.length > 0) { + this.continuationCharacter = settingsContinuationCharacter; + } + else { + this.continuationCharacter = process.platform === "win32" ? "`" : "\\"; + } + } private getSelectedCommand(source: TextEditor) { const commandPrefix = "az"; @@ -270,27 +283,16 @@ class RunLineInEditor { } private stripComments(text: string) { - // allow for single line comments (whole line or on the same line as the command) - // var i = text.search("//"); - // if (i !== -1) { - // return text.substring(0, i) - // } - if (text.trim().startsWith("#")) { - return this.continuationCharacter; // don't let a comment terminate a sequence of command fragments - } - - var i = text.search(" #"); - if (i !== -1) { - return text.substring(0, i) + return this.continuationCharacter; // don't let a whole line comment terminate a sequence of command fragments } - i = text.search(this.continuationCharacter + "#"); + var i = text.search("#"); if (i !== -1) { - return text.substring(0, i+1) + return text.substring(0, i); } - // default is no comments found + // no comment found return text; } From 843c25694de9e55e68c5c7526b2dc55d7ff23207 Mon Sep 17 00:00:00 2001 From: mburleigh Date: Mon, 23 Dec 2019 08:56:29 -0500 Subject: [PATCH 16/17] add isEmbeddedInString() --- src/extension.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 8af8780..c0af474 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -287,8 +287,12 @@ class RunLineInEditor { return this.continuationCharacter; // don't let a whole line comment terminate a sequence of command fragments } - var i = text.search("#"); + var i = text.indexOf("#"); if (i !== -1) { + // account for hash characters that are embedded in strings in the JMESPath query + while (this.isEmbeddedInString(text, i)) { + i = text.indexOf("#", i + 1); // find next # + } return text.substring(0, i); } @@ -296,6 +300,21 @@ class RunLineInEditor { return text; } + // true if the specified position is in a string literal (surrounded by single quotes) + private isEmbeddedInString(text: string, position: number) : boolean { + var stringStart = text.indexOf("'"); // start of string literal + if (stringStart !== -1) { + while (stringStart !== -1) { + var stringEnd = text.indexOf("'", stringStart + 1); // end of string literal + if ((stringEnd !== -1) && (stringStart < position) && (stringEnd > position)) { + return true; // the given position is embedded in a string literal + } + stringStart = text.indexOf("'", stringEnd + 1); + } + } + return false; + } + private commandFinished(startTime: number) { this.runningCommandCount -= 1 this.statusBarItemText = 'Azure CLI: Executed in ' + (Date.now() - startTime) + ' milliseconds'; From 2c1fed41dbb706a96a8c1c1d7344faebbbf94dbb Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 3 Feb 2020 12:07:45 +0100 Subject: [PATCH 17/17] Include 'Line' in settings id --- examples/webapp.azcli | 9 ++++++--- package.json | 2 +- src/extension.ts | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/examples/webapp.azcli b/examples/webapp.azcli index 1e4b520..c077740 100644 --- a/examples/webapp.azcli +++ b/examples/webapp.azcli @@ -1,5 +1,5 @@ # Login and set default subscription -az login +az login az account set --subscription "Try Out Subscription" # Create resource group @@ -8,11 +8,14 @@ az configure --defaults group=tryout-group # Create web app az appservice plan create --name tryout-plan --is-linux --sku B2 -az webapp create --name tryout-web --plan tryout-plan +az webapp create --name tryout-web --plan tryout-plan --runtime "node|6.2" az configure --defaults web=tryout-web # Manual deployment from source repository -az webapp deployment source config --repo-url "https://github.com/Azure-Samples/app-service-web-html-get-started.git" --branch master --manual-integration +az webapp deployment source config \ + --repo-url "https://github.com/Azure-Samples/app-service-web-html-get-started.git" \ + --branch master \ + --manual-integration # Or from docker image az webapp config container set --docker-custom-image-name tutum/hello-world az webapp browse diff --git a/package.json b/package.json index 70e969f..3c1451e 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "scope": "resource", "description": "Controls whether showing the result from running an Azure CLI command in an editor should always create a new editor." }, - "azureCLI.continuationCharacter": { + "azureCLI.lineContinuationCharacter": { "type": "string", "default": "", "scope": "resource", diff --git a/src/extension.ts b/src/extension.ts index c0af474..94ebe40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -174,7 +174,7 @@ class RunLineInEditor { private runningCommandCount : number = 0; private run(source: TextEditor) { - this.RefreshContinuationCharacter(); + this.refreshContinuationCharacter(); const command = this.getSelectedCommand(source); if (command.length > 0) { this.runningCommandCount += 1; @@ -210,9 +210,9 @@ class RunLineInEditor { } } - private RefreshContinuationCharacter() { + private refreshContinuationCharacter() { // the continuation character setting can be changed after the extension is loaded - const settingsContinuationCharacter = workspace.getConfiguration('azureCLI', null).get('continuationCharacter', ""); + const settingsContinuationCharacter = workspace.getConfiguration('azureCLI', null).get('lineContinuationCharacter', ""); if (settingsContinuationCharacter.length > 0) { this.continuationCharacter = settingsContinuationCharacter; }