Change:
Testing With the following settings: I saw the terminal migration notification and then clicked "Migrate", and my settings changed to: Which is defintely not what I expected to happen.
Perhaps if the shell/shellArgs match one of the detected profiles, the name of that profile can be used? So in the sample above,
I'm also kinda confused by this... here's what I had: and it turned it into:
The behavior should be: If there is an exact match (eg. just ), it should assign the existing profile as the default (). If there is not an exact match (eg. ), it should add the profile to the list and set it as the default. As for the name maybe something like would be best.
To verify, see and try other combinations
or could you pls verify this?
with the following: I don't get a notification. Is that expected?
If you've already said don't show again to the notification in that workspace, yes.
ah... I hit the which didn't show the notification until I reloaded the window.
that was another fix that went in to make sure we're not annoying
Migration doesn't seem to be working for me. Here's a gif: ! These are the same settings as here
Ah looks like if I already define then this migration doesn't work. If I delete that setting (and use instead as the default profile, the migration works.
Meaning if you set the to the name of a profile that already exists within your it doesn't work? I think that's by design.
Gotcha. The only thing I'll mention is this, with a defined does not prompt me: but this without it defined does prompt me:
I guess I would expect the migration to still happen (and a new profile be to my existing ) but maybe that's out of scope.
That seems wrong. It should happen as long as you haven't said don't show again. I'll try it.
ah... just opened dev tools:
it's working for me on Windows.
Hmm what does that mean?
Here's a repro on non-Windows without PowerShell:
any errors in dev tools with that repro? can you turn on trace logging? and search for 'migrat'
Beyond this: I don't see anything... which Output should I dig into specifically?
Run set log level -trace -open developer tools. you should see a migration message. migrated from shell/shellArgs...
I wonder if this could be the issue
Hmmm I don't see
The fix here is probably:
Even better is marking the type as readonly too:
We might be able to catch this in the future with
I don't understand what's going wrong here though - if userValue is undefined, profiles get set to already
I'd think making it readonly would be even worse since then we can't add properties to it - such as the newly created profile
Which works fine, the failure case was when had profiles set. The there just corrects the type we're receiving from the config service
ok, should this be a candidate or moved to June?
I think it meets the candidate bar, we really want migration to be solid

Discussion:
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'; <del> import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix } from 'vs/platform/terminal/common/terminal'; </del> <ins> import { IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, TerminalShellType, TerminalSettingId, TitleEventSource, TerminalIcon, TerminalSettingPrefix, ITerminalProfileObject } from 'vs/platform/terminal/common/terminal'; </ins> import { IProductService } from 'vs/platform/product/common/productService'; import { formatMessageForTerminal } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { AutoOpenBarrier } from 'vs/base/common/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); <del> if (profile) { this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile.profileName); this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, undefined); this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, undefined); this._logService.trace(`migrated from shell/shellArgs, ${shell} ${shellArgs} to profile ${JSON.stringify(profile)}`); </del> <ins> if (typeof profile === 'string') { await this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile); this._logService.trace(`migrated from shell/shellArgs, using existing profile ${profile}`); </ins> } else { <del> this._logService.trace('migration from shell/shellArgs to profile did not occur bc created profile was an exact match for existing one', shell, shellArgs); </del> <ins> const profiles = this._configurationService.inspect<{ [key: string]: ITerminalProfileObject }>(TerminalSettingPrefix.Profiles + platform).userValue || {}; const profileConfig: ITerminalProfileObject = { path: profile.path }; if (profile.args) { profileConfig.args = profile.args; } profiles[profile.profileName] = profileConfig; await this._configurationService.updateValue(TerminalSettingPrefix.Profiles + platform, profiles); await this._configurationService.updateValue(TerminalSettingPrefix.DefaultProfile + platform, profile.profileName); this._logService.trace(`migrated from shell/shellArgs, ${shell} ${shellArgs} to profile ${JSON.stringify(profile)}`); </ins> } <ins> await this._configurationService.updateValue(TerminalSettingPrefix.Shell + platform, undefined); await this._configurationService.updateValue(TerminalSettingPrefix.ShellArgs + platform, undefined); </ins> } } as IPromptChoice, ],

import { debounce } from 'vs/base/common/decorators'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { URI, UriComponents } from 'vs/base/common/uri'; <ins> import { equals } from 'vs/base/common/arrays'; </ins> export interface IProfileContextProvider { getDefaultSystemShell: (remoteAuthority: string | undefined, os: OperatingSystem) => Promise<string>;

return false; } <del> async createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined> { const detectedProfile = this._terminalService.availableProfiles?.find(p => p.path === shell); </del> <ins> async createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string> { const detectedProfile = this._terminalService.availableProfiles?.find(p => { if (p.path !== shell) { return false; } if (p.args === undefined || typeof p.args === 'string') { return p.args === shellArgs; } return p.path === shell && equals(p.args, (shellArgs || []) as string[]); }); </ins> const fallbackProfile = (await this.getDefaultProfile({ remoteAuthority: this._remoteAgentService.getConnection()?.remoteAuthority, os: this._primaryBackendOs! })); <ins> fallbackProfile.profileName = `${fallbackProfile.path} (migrated)`; </ins> const profile = detectedProfile || fallbackProfile; const args = this._isValidShellArgs(shellArgs, this._primaryBackendOs!) ? shellArgs : profile.args; const createdProfile = {

isDefault: true }; if (detectedProfile && detectedProfile.profileName === createdProfile.profileName && detectedProfile.path === createdProfile.path && this._argsMatch(detectedProfile.args, createdProfile.args)) { <del> return undefined; </del> <ins> return detectedProfile.profileName; </ins> } return createdProfile; }

getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string>; getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]>; getEnvironment(remoteAuthority: string | undefined): Promise<IProcessEnvironment>; <del> createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined>; </del> <ins> createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string>; </ins> } export interface IShellLaunchConfigResolveOptions {

async getEnvironment(): Promise<IProcessEnvironment> { return process.env; } getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; } getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; } <del> createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | undefined> { throw new Error('Method not implemented.'); } </del> <ins> createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<string | ITerminalProfile> { throw new Error('Method not implemented.'); } </ins> } export class TestLocalTerminalService implements ILocalTerminalService {