Adopt VS Code's terminal profile API for cloud shell (#344)

This commit is contained in:
Will Lorey 2021-10-29 11:53:03 -07:00 коммит произвёл GitHub
Родитель 0f79e272b9
Коммит 1885a843af
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 103 добавлений и 52 удалений

11
package-lock.json сгенерированный
Просмотреть файл

@ -61,7 +61,7 @@
"webpack-cli": "^4.5.0"
},
"engines": {
"vscode": "^1.53.0"
"vscode": "^1.58.0"
}
},
"node_modules/@azure/abort-controller": {
@ -8590,7 +8590,7 @@
},
"node_modules/safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"dependencies": {
@ -9095,6 +9095,11 @@
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": {
"node": ">=0.10.0"
}
@ -18313,7 +18318,7 @@
},
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {

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

@ -17,7 +17,7 @@
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
"publisher": "ms-vscode",
"engines": {
"vscode": "^1.53.0"
"vscode": "^1.58.0"
},
"categories": [
"Azure"
@ -31,10 +31,10 @@
"onCommand:azure-account.loginToCloud",
"onCommand:azure-account.loginWithDeviceCode",
"onCommand:azure-account.logout",
"onCommand:azure-account.openCloudConsoleLinux",
"onCommand:azure-account.openCloudConsoleWindows",
"onCommand:azure-account.selectSubscriptions",
"onCommand:azure-account.uploadFileCloudConsole"
"onCommand:azure-account.uploadFileCloudConsole",
"onTerminalProfile:azure-account.cloudShellBash",
"onTerminalProfile:azure-account.cloudShellPowerShell"
],
"main": "./main",
"contributes": {
@ -64,16 +64,6 @@
"title": "%azure-account.commands.logout%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.openCloudConsoleLinux",
"title": "%azure-account.commands.openCloudConsoleLinux%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.openCloudConsoleWindows",
"title": "%azure-account.commands.openCloudConsoleWindows%",
"category": "%azure-account.commands.azure%"
},
{
"command": "azure-account.selectSubscriptions",
"title": "%azure-account.commands.selectSubscriptions%",
@ -101,6 +91,20 @@
}
]
},
"terminal": {
"profiles": [
{
"title": "%azure-account.cloudShellBash%",
"id": "azure-account.cloudShellBash",
"icon": "azure"
},
{
"title": "%azure-account.cloudShellPowerShell%",
"id": "azure-account.cloudShellPowerShell",
"icon": "azure"
}
]
},
"configuration": {
"type": "object",
"title": "Azure configuration",

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

@ -1,12 +1,12 @@
{
"azure-account.cloudShellBash": "Azure Cloud Shell (Bash)",
"azure-account.cloudShellPowerShell": "Azure Cloud Shell (PowerShell)",
"azure-account.commands.azure": "Azure",
"azure-account.commands.createAccount": "Create an Account",
"azure-account.commands.login": "Sign In",
"azure-account.commands.loginToCloud": "Sign In to Azure Cloud",
"azure-account.commands.loginWithDeviceCode": "Sign In with Device Code",
"azure-account.commands.logout": "Sign Out",
"azure-account.commands.openCloudConsoleLinux": "Open Bash in Cloud Shell",
"azure-account.commands.openCloudConsoleWindows": "Open PowerShell in Cloud Shell",
"azure-account.commands.selectSubscriptions": "Select Subscriptions",
"azure-account.commands.uploadFileCloudConsole": "Upload to Cloud Shell"
}

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

@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Terminal, TerminalProfile } from "vscode";
import { CloudShell, CloudShellStatus } from "../azure-account.api";
export interface CloudShellInternal extends Omit<CloudShell, 'terminal'> {
status: CloudShellStatus;
terminal?: Promise<Terminal>;
terminalProfile?: Promise<TerminalProfile>;
}

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

@ -14,7 +14,7 @@ import * as path from 'path';
import * as semver from 'semver';
import { parse, UrlWithStringQuery } from 'url';
import { v4 as uuid } from 'uuid';
import { commands, Disposable, env, EventEmitter, MessageItem, QuickPickItem, Terminal, Uri, window } from 'vscode';
import { CancellationToken, commands, Disposable, env, EventEmitter, MessageItem, QuickPickItem, Terminal, TerminalOptions, TerminalProfile, ThemeIcon, Uri, window } from 'vscode';
import { AzureAccountExtensionApi, AzureLoginStatus, AzureSession, CloudShell, CloudShellStatus, UploadOptions } from '../azure-account.api';
import { AzureSession as AzureSessionLegacy } from '../azure-account.legacy.api';
import { ext } from '../extensionVariables';
@ -23,10 +23,9 @@ import { TelemetryReporter } from '../telemetry';
import { localize } from '../utils/localize';
import { Deferred } from '../utils/promiseUtils';
import { AccessTokens, connectTerminal, ConsoleUris, Errors, getUserSettings, provisionConsole, resetConsole, Size, UserSettings } from './cloudConsoleLauncher';
import { CloudShellInternal } from './CloudShellInternal';
import { createServer, Queue, readJSON, Server } from './ipc';
interface OS {
id: 'linux' | 'windows';
shellName: string;
@ -37,10 +36,6 @@ export type OSName = 'Linux' | 'Windows';
type OSes = { Linux: OS, Windows: OS };
interface CloudShellWritable extends CloudShell {
status: CloudShellStatus
}
export const OSes: OSes = {
Linux: {
id: 'linux',
@ -170,13 +165,14 @@ function getUploadFile(tokens: Promise<AccessTokens>, uris: Promise<ConsoleUris>
}
}
export const shells: CloudShell[] = [];
export function createCloudConsole(api: AzureAccountExtensionApi, reporter: TelemetryReporter, osName: OSName): CloudShell {
export const shells: CloudShellInternal[] = [];
export function createCloudConsole(api: AzureAccountExtensionApi, reporter: TelemetryReporter, osName: OSName, terminalProfileToken?: CancellationToken): CloudShellInternal {
const os: OS = OSes[osName];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let liveServerQueue: Queue<any> | undefined;
const event: EventEmitter<CloudShellStatus> = new EventEmitter<CloudShellStatus>();
let deferredTerminal: Deferred<Terminal>;
let deferredTerminalProfile: Deferred<TerminalProfile>;
let deferredSession: Deferred<AzureSession>;
let deferredTokens: Deferred<AccessTokens>;
const tokensPromise: Promise<AccessTokens> = new Promise<AccessTokens>((resolve, reject) => deferredTokens = { resolve, reject });
@ -184,17 +180,18 @@ export function createCloudConsole(api: AzureAccountExtensionApi, reporter: Tele
const urisPromise: Promise<ConsoleUris> = new Promise<ConsoleUris>((resolve, reject) => deferredUris = { resolve, reject });
let deferredInitialSize: Deferred<Size>;
const initialSizePromise: Promise<Size> = new Promise<Size>((resolve, reject) => deferredInitialSize = { resolve, reject });
const state: CloudShellWritable = {
const state: CloudShellInternal = {
status: 'Connecting',
onStatusChanged: event.event,
waitForConnection,
terminal: new Promise<Terminal>((resolve, reject) => deferredTerminal = { resolve, reject }),
terminalProfile: new Promise<TerminalProfile>((resolve, reject) => deferredTerminalProfile = { resolve, reject }),
session: new Promise<AzureSession>((resolve, reject) => deferredSession = { resolve, reject }),
uploadFile: getUploadFile(tokensPromise, urisPromise)
};
// eslint-disable-next-line @typescript-eslint/no-empty-function
state.terminal.catch(() => { }); // ignore
state.terminal?.catch(() => { }); // ignore
// eslint-disable-next-line @typescript-eslint/no-empty-function
state.session.catch(() => { }); // ignore
shells.push(state);
@ -204,6 +201,7 @@ export function createCloudConsole(api: AzureAccountExtensionApi, reporter: Tele
event.fire(state.status);
if (status === 'Disconnected') {
deferredTerminal.reject(status);
deferredTerminalProfile.reject(status);
deferredSession.reject(status);
deferredTokens.reject(status);
deferredUris.reject(status);
@ -266,14 +264,14 @@ export function createCloudConsole(api: AzureAccountExtensionApi, reporter: Tele
// open terminal
let shellPath: string = path.join(ext.context.asAbsolutePath('bin'), `node.${isWindows ? 'bat' : 'sh'}`);
let modulePath: string = path.join(ext.context.asAbsolutePath('dist'), 'cloudConsoleLauncher');
let cloudConsoleLauncherPath: string = path.join(ext.context.asAbsolutePath('dist'), 'cloudConsoleLauncher');
if (isWindows) {
modulePath = modulePath.replace(/\\/g, '\\\\');
cloudConsoleLauncherPath = cloudConsoleLauncherPath.replace(/\\/g, '\\\\');
}
const shellArgs: string[] = [
process.argv0,
'-e',
`require('${modulePath}').main()`,
`require('${cloudConsoleLauncherPath}').main()`,
];
if (isWindows) {
@ -282,25 +280,47 @@ export function createCloudConsole(api: AzureAccountExtensionApi, reporter: Tele
shellArgs.shift();
}
const terminal: Terminal = window.createTerminal({
name: localize('azure-account.cloudConsole', "{0} in Cloud Shell", os.shellName),
const terminalOptions: TerminalOptions = {
name: localize('azureCloudShell', 'Azure Cloud Shell ({0})', os.shellName),
iconPath: new ThemeIcon('azure'),
shellPath,
shellArgs,
env: {
CLOUD_CONSOLE_IPC: server.ipcHandlePath,
}
});
const subscription: Disposable = window.onDidCloseTerminal(t => {
if (t === terminal) {
liveServerQueue = undefined;
subscription.dispose();
server.dispose();
updateStatus('Disconnected');
}
});
};
const cleanupCloudShell = () => {
liveServerQueue = undefined;
server.dispose();
updateStatus('Disconnected');
}
// Open the appropriate type of VS Code terminal depending on the entry point
if (terminalProfileToken) {
// Entry point: Terminal profile provider
const terminalProfileCloseSubscription = terminalProfileToken.onCancellationRequested(() => {
terminalProfileCloseSubscription.dispose();
cleanupCloudShell();
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
deferredTerminalProfile!.resolve(new TerminalProfile(terminalOptions));
} else {
// Entry point: Extension API
const terminal: Terminal = window.createTerminal(terminalOptions);
const terminalCloseSubscription = window.onDidCloseTerminal(t => {
if (t === terminal) {
terminalCloseSubscription.dispose();
cleanupCloudShell();
}
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
deferredTerminal!.resolve(terminal);
}
liveServerQueue = serverQueue;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
deferredTerminal!.resolve(terminal);
const loginStatus: AzureLoginStatus = await waitForLoginStatus(api);
if (loginStatus !== 'LoggedIn') {

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

@ -5,11 +5,11 @@
import { createReadStream } from 'fs';
import { basename } from 'path';
import { commands, ConfigurationTarget, env, ExtensionContext, ProgressLocation, Uri, window, workspace, WorkspaceConfiguration } from 'vscode';
import { CancellationToken, commands, ConfigurationTarget, env, ExtensionContext, ProgressLocation, Uri, window, workspace, WorkspaceConfiguration } from 'vscode';
import { createApiProvider, createAzExtOutputChannel, createExperimentationService, registerUIExtensionVariables } from 'vscode-azureextensionui';
import { AzureExtensionApiProvider } from 'vscode-azureextensionui/api';
import { AzureAccountExtensionApi } from './azure-account.api';
import { OSes, OSName, shells } from './cloudConsole/cloudConsole';
import { createCloudConsole, OSes, OSName, shells } from './cloudConsole/cloudConsole';
import { cloudSetting, displayName, extensionPrefix, showSignedInEmailSetting } from './constants';
import { ext } from './extensionVariables';
import { AzureLoginHelper } from './login/AzureLoginHelper';
@ -41,9 +41,19 @@ export async function activateInternal(context: ExtensionContext, perfStats: { l
}
context.subscriptions.push(createStatusBarItem(context, azureLoginHelper.api));
context.subscriptions.push(commands.registerCommand('azure-account.createAccount', createAccount));
context.subscriptions.push(commands.registerCommand('azure-account.openCloudConsoleLinux', () => cloudConsole(azureLoginHelper.api, 'Linux')));
context.subscriptions.push(commands.registerCommand('azure-account.openCloudConsoleWindows', () => cloudConsole(azureLoginHelper.api, 'Windows')));
context.subscriptions.push(commands.registerCommand('azure-account.uploadFileCloudConsole', uri => uploadFile(azureLoginHelper.api, uri)));
window.registerTerminalProfileProvider('azure-account.cloudShellBash', {
provideTerminalProfile: (token: CancellationToken) => {
return createCloudConsole(azureLoginHelper.api, reporter, 'Linux', token).terminalProfile;
}
});
window.registerTerminalProfileProvider('azure-account.cloudShellPowerShell', {
provideTerminalProfile: (token: CancellationToken) => {
return createCloudConsole(azureLoginHelper.api, reporter, 'Windows', token).terminalProfile;
}
});
survey(context, reporter);
reporter.sendSanitizedEvent('activate', { 'activationTime': String((perfStats.loadEndTime - perfStats.loadStartTime) / 1000) });

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

@ -71,8 +71,7 @@ export class AzureAccountExtensionApi implements types.AzureAccountExtensionApi
}
public createCloudShell(os: OSName): types.CloudShell {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return createCloudConsole(this, this.azureLoginHelper.reporter, os)!;
return <types.CloudShell>createCloudConsole(this, this.azureLoginHelper.reporter, os);
}
private sendIsLegacyApiTelemetry(eventName: string, isLegacyApi?: boolean): void {