Migrate users from shell -> profile settings (#124615)

This commit is contained in:
Megan Rogge 2021-05-27 10:26:17 -05:00 committed by GitHub
parent d2a0bfb286
commit 9224159b00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 94 additions and 9 deletions

View file

@ -10,6 +10,13 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const enum TerminalSettingPrefix {
Shell = 'terminal.integrated.shell.',
ShellArgs = 'terminal.integrated.shellArgs.',
DefaultProfile = 'terminal.integrated.defaultProfile.',
Profiles = 'terminal.integrated.profiles.'
}
export const enum TerminalSettingId {
ShellLinux = 'terminal.integrated.shell.linux',
ShellMacOs = 'terminal.integrated.shell.osx',

View file

@ -18,7 +18,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
import { INotificationService, IPromptChoice, NeverShowAgainScope, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
@ -46,7 +46,7 @@ import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTy
import { BrowserFeatures } from 'vs/base/browser/canIUse';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon } from 'vs/platform/terminal/common/terminal';
import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal';
import { IProductService } from 'vs/platform/product/common/productService';
import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { AutoOpenBarrier } from 'vs/base/common/async';
@ -66,6 +66,8 @@ import { getColorClass } from 'vs/workbench/contrib/terminal/browser/terminalIco
const SLOW_CANVAS_RENDER_THRESHOLD = 50;
const NUMBER_OF_FRAMES_TO_MEASURE = 20;
const SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY = 'terminals.integrated.profile-migration';
const enum Constants {
/**
* The maximum amount of milliseconds to wait for a container before starting to create the
@ -326,6 +328,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
window.clearTimeout(initialDataEventsTimeout);
}
}));
this.showProfileMigrationNotification();
}
private _getIcon(): TerminalIcon | undefined {
@ -353,6 +356,44 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._register(disposable);
}
async showProfileMigrationNotification(): Promise<void> {
const platform = this._getPlatformKey();
const shouldMigrateToProfile = (!!this._configurationService.getValue(TerminalSettingPrefix.Shell + platform) ||
!!this._configurationService.getValue(TerminalSettingPrefix.ShellArgs + platform)) &&
!!this._configurationService.getValue(TerminalSettingPrefix.DefaultProfile + platform);
if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true)) {
this._notificationService.prompt(
Severity.Info,
nls.localize('terminalProfileMigration', "The terminal is using deprecated shell/shellArgs settings, do you want to migrate it to a profile?"),
[
{
label: nls.localize('migrateToProfile', "Migrate"),
run: async () => {
const shell = this._configurationService.getValue(TerminalSettingPrefix.Shell + platform);
const shellArgs = this._configurationService.getValue(TerminalSettingPrefix.ShellArgs + platform);
const profile = await this._terminalProfileResolverService.createProfileFromShellAndShellArgs(shell, shellArgs);
if (profile) {
this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile.profileName);
this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, null);
this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, null);
this._logService.trace(`migrated from shell/shellArgs, ${shell} ${shellArgs} to profile ${JSON.stringify(profile)}`);
} else {
this._logService.trace('migration from shell/shellArgs to profile did not occur bc created profile was an exact match for existing one', shell, shellArgs);
}
}
} as IPromptChoice,
],
{
neverShowAgain: { id: SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, scope: NeverShowAgainScope.WORKSPACE }
}
);
}
}
private _getPlatformKey(): string {
return isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
private _initDimensions(): void {
// The terminal panel needs to have been created
if (!this._container) {

View file

@ -410,6 +410,45 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
return value;
}
async createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined> {
const detectedProfile = this._terminalService.availableProfiles?.find(p => p.path === shell);
const fallbackProfile = (await this.getDefaultProfile({
remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority,
os: this._primaryBackendOs!
}));
const profile = detectedProfile || fallbackProfile;
const args = this._isValidShellArgs(shellArgs, this._primaryBackendOs!) ? shellArgs : profile.args;
const createdProfile = {
profileName: profile.profileName,
path: profile.path,
args,
isDefault: true
};
if (detectedProfile && detectedProfile.profileName === createdProfile.profileName && detectedProfile.path === createdProfile.path && this._argsMatch(detectedProfile.args, createdProfile.args)) {
return undefined;
}
return createdProfile;
}
private _argsMatch(args1: string | string[] | undefined, args2: string | string[] | undefined): boolean {
if (!args1 && !args2) {
return true;
} else if (typeof args1 === 'string' && typeof args2 === 'string') {
return args1 === args2;
} else if (Array.isArray(args1) && Array.isArray(args2)) {
if (args1.length !== args2.length) {
return false;
}
for (let i = 0; i < args1.length; i++) {
if (args1[i] !== args2[i]) {
return false;
}
}
return true;
}
return false;
}
}
export class BrowserTerminalProfileResolverService extends BaseTerminalProfileResolverService {

View file

@ -17,7 +17,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IEditableData, IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
@ -177,12 +177,8 @@ export class TerminalService implements ITerminalService {
lifecycleService.onWillShutdown(e => this._onWillShutdown(e));
this._configurationService.onDidChangeConfiguration(async e => {
if (e.affectsConfiguration(TerminalSettingId.ProfilesWindows) ||
e.affectsConfiguration(TerminalSettingId.ProfilesMacOs) ||
e.affectsConfiguration(TerminalSettingId.ProfilesLinux) ||
e.affectsConfiguration(TerminalSettingId.DefaultProfileWindows) ||
e.affectsConfiguration(TerminalSettingId.DefaultProfileMacOs) ||
e.affectsConfiguration(TerminalSettingId.DefaultProfileLinux) ||
if (e.affectsConfiguration(TerminalSettingPrefix.DefaultProfile + this._getPlatformKey()) ||
e.affectsConfiguration(TerminalSettingPrefix.Profiles + this._getPlatformKey()) ||
e.affectsConfiguration(TerminalSettingId.UseWslProfiles)) {
this._refreshAvailableProfiles();
}

View file

@ -114,6 +114,7 @@ export interface ITerminalProfileResolverService {
// TODO: Remove when workspace trust is enabled
getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined;
getSafeConfigValueFullKey(key: string): unknown | undefined;
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined>;
}
export interface IShellLaunchConfigResolveOptions {

View file

@ -1591,6 +1591,7 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol
async getEnvironment(): Promise<IProcessEnvironment> { return process.env; }
getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; }
getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; }
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined> { throw new Error('Method not implemented.'); }
}
export class TestLocalTerminalService implements ILocalTerminalService {