Merge pull request #61 from mburleigh/SupportContinuationCharacter

Support multiline commands
This commit is contained in:
Christof Marti 2020-02-03 12:13:58 +01:00 коммит произвёл GitHub
Родитель 0ac34e2214 2c1fed41db
Коммит d8435931f3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 156 добавлений и 42 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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.lineContinuationCharacter": {
"type": "string",
"default": "",
"scope": "resource",
"description": "Override the default continuation character (backtick [`] on Windows otherwise backslash [\\]) used for multiline commands"
}
}
},

Просмотреть файл

@ -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,56 +169,158 @@ 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.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.refreshContinuationCharacter();
const command = this.getSelectedCommand(source);
if (command.length > 0) {
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
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 => {})
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(() => this.commandFinished(t0))
)
.then(undefined, console.error);
.then(undefined, console.error);
}
}
private commandFinished(startTime: number)
{
private refreshContinuationCharacter() {
// the continuation character setting can be changed after the extension is loaded
const settingsContinuationCharacter = workspace.getConfiguration('azureCLI', null).get<string>('lineContinuationCharacter', "");
if (settingsContinuationCharacter.length > 0) {
this.continuationCharacter = settingsContinuationCharacter;
}
else {
this.continuationCharacter = process.platform === "win32" ? "`" : "\\";
}
}
private getSelectedCommand(source: TextEditor) {
const commandPrefix = "az";
if (source.selection.isEmpty) {
var lineNumber = source.selection.active.line;
if (source.document.lineAt(lineNumber).text.length === 0) {
window.showInformationMessage<any>("Please put the cursor on a line that contains a command (or part of a command).");
return "";
}
// 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);
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);
}
return command;
}
else {
// execute only the selected text
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(this.continuationCharacter)) {
command = command.trim().slice(0, -1); // remove continuation character from command
}
var line = this.stripComments(source.document.lineAt(index).text);
if (line.trim().toLowerCase().startsWith(commandPrefix)) {
window.showErrorMessage<any>("Multiple command selection not supported");
return "";
}
// append this line to the command string
if (index === selectionEnd.line) {
command = command + line.substring(0, selectionEnd.character); // only append up to the end of the selection
}
else {
command = command + line;
}
}
return command;
}
}
}
private stripComments(text: string) {
if (text.trim().startsWith("#")) {
return this.continuationCharacter; // don't let a whole line comment terminate a sequence of command fragments
}
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);
}
// no comment found
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';
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