diff --git a/eslint.config.js b/eslint.config.js index 29474886964..09ef2112edd 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -338,24 +338,24 @@ export default tseslint.config( 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', 'src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts', 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', - 'src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts', - 'src/vs/workbench/contrib/terminal/browser/remotePty.ts', - 'src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalActions.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalGroup.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalIcon.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalInstance.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalMenus.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalService.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts', - 'src/vs/workbench/contrib/terminal/browser/terminalView.ts', - 'src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts', + // 'src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts', + // 'src/vs/workbench/contrib/terminal/browser/remotePty.ts', + // 'src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalActions.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalGroup.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalIcon.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalInstance.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalMenus.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalService.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts', + // 'src/vs/workbench/contrib/terminal/browser/terminalView.ts', + // 'src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts', 'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts', 'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts', 'src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.initialHint.contribution.ts', diff --git a/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts index 70ff99db0cf..1b7f3413a6b 100644 --- a/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts @@ -6,6 +6,7 @@ import { Emitter } from '../../../../base/common/event.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; +import { isObject } from '../../../../base/common/types.js'; import { localize } from '../../../../nls.js'; import { ICrossVersionSerializedTerminalState, IPtyHostController, ISerializedTerminalState, ITerminalLogService } from '../../../../platform/terminal/common/terminal.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; @@ -105,20 +106,27 @@ export abstract class BaseTerminalBackend extends Disposable { if (serializedState === undefined) { return undefined; } - const parsedUnknown = JSON.parse(serializedState); - if (!('version' in parsedUnknown) || !('state' in parsedUnknown) || !Array.isArray(parsedUnknown.state)) { - this._logService.warn('Could not revive serialized processes, wrong format', parsedUnknown); + const crossVersionState = JSON.parse(serializedState) as unknown; + if (!isCrossVersionSerializedTerminalState(crossVersionState)) { + this._logService.warn('Could not revive serialized processes, wrong format', crossVersionState); return undefined; } - const parsedCrossVersion = parsedUnknown as ICrossVersionSerializedTerminalState; - if (parsedCrossVersion.version !== 1) { - this._logService.warn(`Could not revive serialized processes, wrong version "${parsedCrossVersion.version}"`, parsedCrossVersion); + if (crossVersionState.version !== 1) { + this._logService.warn(`Could not revive serialized processes, wrong version "${crossVersionState.version}"`, crossVersionState); return undefined; } - return parsedCrossVersion.state as ISerializedTerminalState[]; + return crossVersionState.state as ISerializedTerminalState[]; } protected _getWorkspaceId(): string { return this._workspaceContextService.getWorkspace().id; } } + +function isCrossVersionSerializedTerminalState(obj: unknown): obj is ICrossVersionSerializedTerminalState { + return ( + isObject(obj) && + 'version' in obj && typeof obj.version === 'number' && + 'state' in obj && Array.isArray(obj.state) + ); +} diff --git a/src/vs/workbench/contrib/terminal/browser/remotePty.ts b/src/vs/workbench/contrib/terminal/browser/remotePty.ts index 70933f071ec..42daf1267e2 100644 --- a/src/vs/workbench/contrib/terminal/browser/remotePty.ts +++ b/src/vs/workbench/contrib/terminal/browser/remotePty.ts @@ -8,6 +8,7 @@ import { ITerminalLaunchResult, IProcessPropertyMap, ITerminalChildProcess, ITer import { BasePty } from '../common/basePty.js'; import { RemoteTerminalChannelClient } from '../common/remote/remoteTerminalChannel.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; +import { hasKey } from '../../../../base/common/types.js'; export class RemotePty extends BasePty implements ITerminalChildProcess { private readonly _startBarrier: Barrier; @@ -35,7 +36,7 @@ export class RemotePty extends BasePty implements ITerminalChildProcess { const startResult = await this._remoteTerminalChannel.start(this.id); - if (startResult && 'message' in startResult) { + if (startResult && hasKey(startResult, { message: true })) { // An error occurred return startResult; } diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index 313148f646f..b60f4c9ca42 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -349,7 +349,7 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack this._storageService.remove(TerminalStorageKeys.TerminalLayoutInfo, StorageScope.WORKSPACE); } } catch (e: unknown) { - this._logService.warn('RemoteTerminalBackend#getTerminalLayoutInfo Error', e && typeof e === 'object' && 'message' in e ? e.message : e); + this._logService.warn('RemoteTerminalBackend#getTerminalLayoutInfo Error', (<{ message?: string }>e).message ?? e); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 53c281e498c..74100b6c14c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -14,7 +14,7 @@ import { Schemas } from '../../../../base/common/network.js'; import { isAbsolute } from '../../../../base/common/path.js'; import { isWindows } from '../../../../base/common/platform.js'; import { dirname } from '../../../../base/common/resources.js'; -import { isObject, isString } from '../../../../base/common/types.js'; +import { hasKey, isObject, isString } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; @@ -316,7 +316,10 @@ export function registerTerminalActions() { id: TerminalCommandId.CreateTerminalEditor, title: localize2('workbench.action.terminal.createTerminalEditor', 'Create New Terminal in Editor Area'), run: async (c, _, args) => { - const options = (isObject(args) && 'location' in args) ? args as ICreateTerminalOptions : { location: TerminalLocation.Editor }; + function isCreateTerminalOptions(obj: unknown): obj is ICreateTerminalOptions { + return isObject(obj) && 'location' in obj; + } + const options = isCreateTerminalOptions(args) ? args : { location: TerminalLocation.Editor }; const instance = await c.service.createTerminal(options); await instance.focusWhenReady(); } @@ -998,7 +1001,7 @@ export function registerTerminalActions() { }] }, run: async (c, _, args) => { - const cwd = isObject(args) && 'cwd' in args ? toOptionalString(args.cwd) : undefined; + const cwd = args ? toOptionalString((<{ cwd?: string }>args).cwd) : undefined; const instance = await c.service.createTerminal({ cwd }); if (!instance) { return; @@ -1032,7 +1035,7 @@ export function registerTerminalActions() { f1: false, run: async (activeInstance, c, accessor, args) => { const notificationService = accessor.get(INotificationService); - const name = isObject(args) && 'name' in args ? toOptionalString(args.name) : undefined; + const name = args ? toOptionalString((<{ name?: string }>args).name) : undefined; if (!name) { notificationService.warn(localize('workbench.action.terminal.renameWithArg.noName', "No name argument provided")); return; @@ -1511,9 +1514,13 @@ export function validateTerminalName(name: string): { content: string; severity: return null; } +function isTerminalProfile(obj: unknown): obj is ITerminalProfile { + return isObject(obj) && 'profileName' in obj; +} + function convertOptionsOrProfileToOptions(optionsOrProfile?: ICreateTerminalOptions | ITerminalProfile): ICreateTerminalOptions | undefined { - if (isObject(optionsOrProfile) && 'profileName' in optionsOrProfile) { - return { config: optionsOrProfile as ITerminalProfile, location: (optionsOrProfile as ICreateTerminalOptions).location }; + if (isTerminalProfile(optionsOrProfile)) { + return { config: optionsOrProfile, location: (optionsOrProfile as ICreateTerminalOptions).location }; } return optionsOrProfile; } @@ -1574,13 +1581,16 @@ export function refreshTerminalActions(detectedProfiles: ITerminalProfile[]): ID let instance: ITerminalInstance | undefined; let cwd: string | URI | undefined; - if (isObject(eventOrOptionsOrProfile) && eventOrOptionsOrProfile && 'profileName' in eventOrOptionsOrProfile) { + if (isObject(eventOrOptionsOrProfile) && eventOrOptionsOrProfile && hasKey(eventOrOptionsOrProfile, { profileName: true })) { const config = c.profileService.availableProfiles.find(profile => profile.profileName === eventOrOptionsOrProfile.profileName); if (!config) { throw new Error(`Could not find terminal profile "${eventOrOptionsOrProfile.profileName}"`); } options = { config }; - if ('location' in eventOrOptionsOrProfile) { + function isSimpleArgs(obj: unknown): obj is { profileName: string; location?: 'view' | 'editor' | unknown } { + return isObject(obj) && 'location' in obj; + } + if (isSimpleArgs(eventOrOptionsOrProfile)) { switch (eventOrOptionsOrProfile.location) { case 'editor': options.location = TerminalLocation.Editor; break; case 'view': options.location = TerminalLocation.Panel; break; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts index d59f618e611..126986d15d9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorSerializer.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isObject } from '../../../../base/common/types.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IEditorSerializer } from '../../../common/editor.js'; import { EditorInput } from '../../../common/editor/editorInput.js'; -import { ISerializedTerminalEditorInput, ITerminalEditorService, ITerminalInstance } from './terminal.js'; +import { ISerializedTerminalEditorInput, ITerminalEditorService, ITerminalInstance, type IDeserializedTerminalEditorInput } from './terminal.js'; import { TerminalEditorInput } from './terminalEditorInput.js'; export class TerminalInputSerializer implements IEditorSerializer { @@ -26,8 +27,11 @@ export class TerminalInputSerializer implements IEditorSerializer { } public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | undefined { - const terminalInstance = JSON.parse(serializedEditorInput); - return this._terminalEditorService.reviveInput(terminalInstance); + const editorInput = JSON.parse(serializedEditorInput) as unknown; + if (!isDeserializedTerminalEditorInput(editorInput)) { + throw new Error(`Could not revive terminal editor input, ${editorInput}`); + } + return this._terminalEditorService.reviveInput(editorInput); } private _toJson(instance: ITerminalInstance): ISerializedTerminalEditorInput { @@ -47,3 +51,7 @@ export class TerminalInputSerializer implements IEditorSerializer { }; } } + +function isDeserializedTerminalEditorInput(obj: unknown): obj is IDeserializedTerminalEditorInput { + return isObject(obj) && 'id' in obj && 'pid' in obj; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 6190a6693fa..ab3bb97d0ae 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -231,15 +231,11 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor } reviveInput(deserializedInput: IDeserializedTerminalEditorInput): EditorInput { - if ('pid' in deserializedInput) { - const newDeserializedInput = { ...deserializedInput, findRevivedId: true }; - const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor); - const input = this._instantiationService.createInstance(TerminalEditorInput, instance.resource, instance); - this._registerInstance(instance.resource.path, input, instance); - return input; - } else { - throw new Error(`Could not revive terminal editor input, ${deserializedInput}`); - } + const newDeserializedInput = { ...deserializedInput, findRevivedId: true }; + const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor); + const input = this._instantiationService.createInstance(TerminalEditorInput, instance.resource, instance); + this._registerInstance(instance.resource.path, input, instance); + return input; } detachInstance(instance: ITerminalInstance) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 6be0f71d5e9..6dfcd20541a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -16,7 +16,7 @@ import { TerminalStatus } from './terminalStatusList.js'; import { getWindow } from '../../../../base/browser/dom.js'; import { getPartByLocation } from '../../../services/views/browser/viewsService.js'; import { asArray } from '../../../../base/common/arrays.js'; -import type { SingleOrMany } from '../../../../base/common/types.js'; +import { hasKey, type SingleOrMany } from '../../../../base/common/types.js'; const enum Constants { /** @@ -303,7 +303,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { // if a parent terminal is provided, find it // otherwise, parent is the active terminal const parentIndex = parentTerminalId ? this._terminalInstances.findIndex(t => t.instanceId === parentTerminalId) : this._activeInstanceIndex; - if ('instanceId' in shellLaunchConfigOrInstance) { + if (hasKey(shellLaunchConfigOrInstance, { instanceId: true })) { instance = shellLaunchConfigOrInstance; } else { instance = this._terminalInstanceService.createInstance(shellLaunchConfigOrInstance, TerminalLocation.Panel); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index 1f21581a19d..791c9dafea0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -16,6 +16,7 @@ import { ITerminalProfileResolverService } from '../common/terminal.js'; import { ansiColorMap } from '../common/terminalColorRegistry.js'; import { createStyleSheet } from '../../../../base/browser/domStylesheets.js'; import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; +import { isString } from '../../../../base/common/types.js'; export function getColorClass(colorKey: string): string; @@ -108,9 +109,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr } } - if (icon instanceof URI) { + if (URI.isUri(icon)) { uri = icon; - } else if (icon instanceof Object && 'light' in icon && 'dark' in icon) { + } else if (!ThemeIcon.isThemeIcon(icon) && !isString(icon)) { uri = isDark(colorScheme) ? icon.dark : icon.light; } if (uri instanceof URI) { @@ -123,8 +124,11 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr } export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string { - if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) { - return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id; + if (isString(terminal.icon)) { + return terminal.icon; } - return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id; + if (ThemeIcon.isThemeIcon(terminal.icon)) { + return terminal.icon.id; + } + return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts b/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts index fd222eda77e..33449621a20 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts @@ -8,7 +8,7 @@ import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js' import { codiconsLibrary } from '../../../../base/common/codiconsLibrary.js'; import { Lazy } from '../../../../base/common/lazy.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; -import type { ThemeIcon } from '../../../../base/common/themables.js'; +import { ThemeIcon } from '../../../../base/common/themables.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { ILayoutService } from '../../../../platform/layout/browser/layoutService.js'; @@ -23,7 +23,7 @@ const icons = new Lazy(() => { if (e.id === codiconsLibrary.blank.id) { return false; } - if (!('fontCharacter' in e.defaults)) { + if (ThemeIcon.isThemeIcon(e.defaults)) { return false; } if (includedChars.has(e.defaults.fontCharacter)) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 35fc962eece..7444f249616 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -93,6 +93,7 @@ import type { IProgressState } from '@xterm/addon-progress'; import { refreshShellIntegrationInfoStatus } from './terminalTooltip.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { PromptInputState } from '../../../../platform/terminal/common/capabilities/commandDetection/promptInputModel.js'; +import { hasKey } from '../../../../base/common/types.js'; const enum Constants { /** @@ -645,13 +646,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._register(this.onDisposed(() => { contribution.dispose(); this._contributions.delete(desc.id); - // Just in case to prevent potential future memory leaks due to cyclic dependency. - if ('instance' in contribution) { - delete contribution.instance; - } - if ('_instance' in contribution) { - delete contribution._instance; - } })); } } @@ -1556,9 +1550,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const originalIcon = this.shellLaunchConfig.icon; await this._processManager.createProcess(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows).then(result => { if (result) { - if ('message' in result) { + if (hasKey(result, { message: true })) { this._onProcessExit(result); - } else if ('injectedArgs' in result) { + } else if (hasKey(result, { injectedArgs: true })) { this._injectedArgs = result.injectedArgs; } } @@ -1832,9 +1826,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._shellLaunchConfig = shell; // Must be done before calling _createProcess() await this._processManager.relaunch(this._shellLaunchConfig, this._cols || Constants.DefaultCols, this._rows || Constants.DefaultRows, reset).then(result => { if (result) { - if ('message' in result) { + if (hasKey(result, { message: true })) { this._onProcessExit(result); - } else if ('injectedArgs' in result) { + } else if (hasKey(result, { injectedArgs: true })) { this._injectedArgs = result.injectedArgs; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index d1f7b5d8adc..acb65754e1a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -16,6 +16,7 @@ import { TerminalContextKeys } from '../common/terminalContextKey.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { promiseWithResolvers } from '../../../../base/common/async.js'; +import { hasKey } from '../../../../base/common/types.js'; export class TerminalInstanceService extends Disposable implements ITerminalInstanceService { declare _serviceBrand: undefined; @@ -54,7 +55,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst convertProfileToShellLaunchConfig(shellLaunchConfigOrProfile?: IShellLaunchConfig | ITerminalProfile, cwd?: string | URI): IShellLaunchConfig { // Profile was provided - if (shellLaunchConfigOrProfile && 'profileName' in shellLaunchConfigOrProfile) { + if (shellLaunchConfigOrProfile && hasKey(shellLaunchConfigOrProfile, { profileName: true })) { const profile = shellLaunchConfigOrProfile; if (!profile.path) { return shellLaunchConfigOrProfile; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 20358187449..8436f9a3b32 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -19,6 +19,7 @@ import { terminalStrings } from '../common/terminalStrings.js'; import { ACTIVE_GROUP, AUX_WINDOW_GROUP, SIDE_GROUP } from '../../../services/editor/common/editorService.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { HasSpeechProvider } from '../../speech/common/speechService.js'; +import { hasKey } from '../../../../base/common/types.js'; export const enum TerminalContextMenuGroup { Chat = '0_chat', @@ -787,7 +788,7 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro } { const dropdownActions: IAction[] = []; const submenuActions: IAction[] = []; - const splitLocation = (location === TerminalLocation.Editor || (typeof location === 'object' && 'viewColumn' in location && location.viewColumn === ACTIVE_GROUP)) ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true }; + const splitLocation = (location === TerminalLocation.Editor || (typeof location === 'object' && hasKey(location, { viewColumn: true }) && location.viewColumn === ACTIVE_GROUP)) ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true }; if (location === TerminalLocation.Editor) { location = { viewColumn: ACTIVE_GROUP }; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index eff79840baf..5b4bc57c92a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -18,6 +18,7 @@ import { IPickerQuickAccessItem } from '../../../../platform/quickinput/browser/ import { getIconRegistry } from '../../../../platform/theme/common/iconRegistry.js'; import { basename } from '../../../../base/common/path.js'; import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +import { hasKey } from '../../../../base/common/types.js'; type DefaultProfileName = string; @@ -40,9 +41,7 @@ export class TerminalProfileQuickpick { return; } if (type === 'setDefault') { - if ('command' in result.profile) { - return; // Should never happen - } else if ('id' in result.profile) { + if (hasKey(result.profile, { id: true })) { // extension contributed profile await this._configurationService.updateValue(defaultProfileKey, result.profile.title, ConfigurationTarget.USER); return { @@ -60,7 +59,7 @@ export class TerminalProfileQuickpick { } // Add the profile to settings if necessary - if ('isAutoDetected' in result.profile) { + if (hasKey(result.profile, { profileName: true })) { const profilesConfig = await this._configurationService.getValue(profilesKey); if (typeof profilesConfig === 'object') { const newProfile: ITerminalProfileObject = { @@ -76,7 +75,7 @@ export class TerminalProfileQuickpick { // Set the default profile await this._configurationService.updateValue(defaultProfileKey, result.profileName, ConfigurationTarget.USER); } else if (type === 'createInstance') { - if ('id' in result.profile) { + if (hasKey(result.profile, { id: true })) { return { config: { extensionIdentifier: result.profile.extensionIdentifier, @@ -94,7 +93,7 @@ export class TerminalProfileQuickpick { } } // for tests - return 'profileName' in result.profile ? result.profile.profileName : result.profile.title; + return hasKey(result.profile, { profileName: true }) ? result.profile.profileName : result.profile.title; } private async _createAndShow(type: 'setDefault' | 'createInstance'): Promise { @@ -110,10 +109,7 @@ export class TerminalProfileQuickpick { if (!await this._isProfileSafe(context.item.profile)) { return; } - if ('command' in context.item.profile) { - return; - } - if ('id' in context.item.profile) { + if (hasKey(context.item.profile, { id: true })) { return; } const configProfiles: { [key: string]: any } = this._configurationService.getValue(TerminalSettingPrefix.Profiles + platformKey); @@ -223,8 +219,8 @@ export class TerminalProfileQuickpick { } private async _isProfileSafe(profile: ITerminalProfile | IExtensionTerminalProfile): Promise { - const isUnsafePath = 'isUnsafePath' in profile && profile.isUnsafePath; - const requiresUnsafePath = 'requiresUnsafePath' in profile && profile.requiresUnsafePath; + const isUnsafePath = hasKey(profile, { profileName: true }) && profile.isUnsafePath; + const requiresUnsafePath = hasKey(profile, { profileName: true }) && profile.requiresUnsafePath; if (!isUnsafePath && !requiresUnsafePath) { return true; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 0bc0700b6fd..b40cabd7aea 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -169,7 +169,7 @@ export abstract class BaseTerminalProfileResolverService extends Disposable impl return this._context.getEnvironment(remoteAuthority); } - private _getCustomIcon(icon?: unknown): TerminalIcon | undefined { + private _getCustomIcon(icon?: TerminalIcon): TerminalIcon | undefined { if (!icon) { return undefined; } @@ -182,11 +182,8 @@ export abstract class BaseTerminalProfileResolverService extends Disposable impl if (URI.isUri(icon) || isUriComponents(icon)) { return URI.revive(icon); } - if (typeof icon === 'object' && 'light' in icon && 'dark' in icon) { - const castedIcon = (icon as { light: unknown; dark: unknown }); - if ((URI.isUri(castedIcon.light) || isUriComponents(castedIcon.light)) && (URI.isUri(castedIcon.dark) || isUriComponents(castedIcon.dark))) { - return { light: URI.revive(castedIcon.light), dark: URI.revive(castedIcon.dark) }; - } + if ((URI.isUri(icon.light) || isUriComponents(icon.light)) && (URI.isUri(icon.dark) || isUriComponents(icon.dark))) { + return { light: URI.revive(icon.light), dark: URI.revive(icon.dark) }; } return undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 0a7f1950e25..ead592e0bcd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -23,6 +23,7 @@ import { ITerminalContributionService } from '../common/terminalExtensionPoints. import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js'; +import { hasKey } from '../../../../base/common/types.js'; /* * Links TerminalService with TerminalProfileResolverService @@ -244,7 +245,7 @@ export class TerminalProfileService extends Disposable implements ITerminalProfi async getContributedDefaultProfile(shellLaunchConfig: IShellLaunchConfig): Promise { // prevents recursion with the MainThreadTerminalService call to create terminal // and defers to the provided launch config when an executable is provided - if (shellLaunchConfig && !shellLaunchConfig.extHostTerminalId && !('executable' in shellLaunchConfig)) { + if (shellLaunchConfig && !shellLaunchConfig.extHostTerminalId && !hasKey(shellLaunchConfig, { executable: true })) { const key = await this.getPlatformKey(); const defaultProfileName = this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${key}`); const contributedDefaultProfile = this.contributedProfiles.find(p => p.title === defaultProfileName); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 2a89cf343a7..3f9099f4ca2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -56,6 +56,7 @@ import { createInstanceCapabilityEventMultiplexer } from './terminalEvents.js'; import { isAuxiliaryWindow, mainWindow } from '../../../../base/browser/window.js'; import { GroupIdentifier } from '../../../common/editor.js'; import { getActiveWindow } from '../../../../base/browser/dom.js'; +import { hasKey } from '../../../../base/common/types.js'; interface IBackgroundTerminal { instance: ITerminalInstance; @@ -251,14 +252,14 @@ export class TerminalService extends Disposable implements ITerminalService { const defaultLocation = this._terminalConfigurationService.defaultLocation; let instance; - if (result.config && 'id' in result?.config) { + if (result.config && hasKey(result.config, { id: true })) { await this.createContributedTerminalProfile(result.config.extensionIdentifier, result.config.id, { icon: result.config.options?.icon, color: result.config.options?.color, location: !!(keyMods?.alt && activeInstance) ? { splitActiveTerminal: true } : defaultLocation }); return; - } else if (result.config && 'profileName' in result.config) { + } else if (result.config && hasKey(result.config, { profileName: true })) { if (keyMods?.alt && activeInstance) { // create split, only valid if there's an active instance instance = await this.createTerminal({ location: { parentTerminal: activeInstance }, config: result.config, cwd }); @@ -951,9 +952,9 @@ export class TerminalService extends Disposable implements ITerminalService { if (location === TerminalLocation.Editor) { return this._terminalEditorService; } else if (typeof location === 'object') { - if ('viewColumn' in location) { + if (hasKey(location, { viewColumn: true })) { return this._terminalEditorService; - } else if ('parentTerminal' in location) { + } else if (hasKey(location, { parentTerminal: true })) { return (await location.parentTerminal).target === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; } } else { @@ -968,7 +969,7 @@ export class TerminalService extends Disposable implements ITerminalService { // local terminal in a remote workspace as profile won't be used in those cases and these // terminals need to be launched before remote connections are established. if (this._terminalProfileService.availableProfiles.length === 0) { - const isPtyTerminal = options?.config && 'customPtyImplementation' in options.config; + const isPtyTerminal = options?.config && hasKey(options.config, { customPtyImplementation: true }); const isLocalInRemoteTerminal = this._remoteAgentService.getConnection() && URI.isUri(options?.cwd) && options?.cwd.scheme === Schemas.vscodeFileResource; if (!isPtyTerminal && !isLocalInRemoteTerminal) { if (this._connectionState === TerminalConnectionState.Connecting) { @@ -982,12 +983,14 @@ export class TerminalService extends Disposable implements ITerminalService { } const config = options?.config || this._terminalProfileService.getDefaultProfile(); - const shellLaunchConfig = config && 'extensionIdentifier' in config ? {} : this._terminalInstanceService.convertProfileToShellLaunchConfig(config || {}); + const shellLaunchConfig = config && hasKey(config, { extensionIdentifier: true }) ? {} : this._terminalInstanceService.convertProfileToShellLaunchConfig(config || {}); // Get the contributed profile if it was provided const contributedProfile = options?.skipContributedProfileCheck ? undefined : await this._getContributedProfile(shellLaunchConfig, options); - const splitActiveTerminal = typeof options?.location === 'object' && 'splitActiveTerminal' in options.location ? options.location.splitActiveTerminal : typeof options?.location === 'object' ? 'parentTerminal' in options.location : false; + const splitActiveTerminal = typeof options?.location === 'object' && hasKey(options.location, { splitActiveTerminal: true }) + ? options.location.splitActiveTerminal + : typeof options?.location === 'object' ? hasKey(options.location, { parentTerminal: true }) : false; await this._resolveCwd(shellLaunchConfig, splitActiveTerminal, options); @@ -1000,7 +1003,7 @@ export class TerminalService extends Disposable implements ITerminalService { if (splitActiveTerminal) { location = resolvedLocation === TerminalLocation.Editor ? { viewColumn: SIDE_GROUP } : { splitActiveTerminal: true }; } else { - location = typeof options?.location === 'object' && 'viewColumn' in options.location ? options.location : resolvedLocation; + location = typeof options?.location === 'object' && hasKey(options.location, { viewColumn: true }) ? options.location : resolvedLocation; } await this.createContributedTerminalProfile(contributedProfile.extensionIdentifier, contributedProfile.id, { icon: contributedProfile.icon, @@ -1063,7 +1066,7 @@ export class TerminalService extends Disposable implements ITerminalService { } private async _getContributedProfile(shellLaunchConfig: IShellLaunchConfig, options?: ICreateTerminalOptions): Promise { - if (options?.config && 'extensionIdentifier' in options.config) { + if (options?.config && hasKey(options.config, { extensionIdentifier: true })) { return options.config; } @@ -1100,7 +1103,7 @@ export class TerminalService extends Disposable implements ITerminalService { shellLaunchConfig.cwd = options.cwd; } else if (splitActiveTerminal && options?.location) { let parent = this.activeInstance; - if (typeof options.location === 'object' && 'parentTerminal' in options.location) { + if (typeof options.location === 'object' && hasKey(options.location, { parentTerminal: true })) { parent = await options.location.parentTerminal; } if (!parent) { @@ -1152,13 +1155,13 @@ export class TerminalService extends Disposable implements ITerminalService { async resolveLocation(location?: ITerminalLocationOptions): Promise { if (location && typeof location === 'object') { - if ('parentTerminal' in location) { + if (hasKey(location, { parentTerminal: true })) { // since we don't set the target unless it's an editor terminal, this is necessary const parentTerminal = await location.parentTerminal; return !parentTerminal.target ? TerminalLocation.Panel : parentTerminal.target; - } else if ('viewColumn' in location) { + } else if (hasKey(location, { viewColumn: true })) { return TerminalLocation.Editor; - } else if ('splitActiveTerminal' in location) { + } else if (hasKey(location, { splitActiveTerminal: true })) { // since we don't set the target unless it's an editor terminal, this is necessary return !this._activeInstance?.target ? TerminalLocation.Panel : this._activeInstance?.target; } @@ -1167,16 +1170,16 @@ export class TerminalService extends Disposable implements ITerminalService { } private async _getSplitParent(location?: ITerminalLocationOptions): Promise { - if (location && typeof location === 'object' && 'parentTerminal' in location) { + if (location && typeof location === 'object' && hasKey(location, { parentTerminal: true })) { return location.parentTerminal; - } else if (location && typeof location === 'object' && 'splitActiveTerminal' in location) { + } else if (location && typeof location === 'object' && hasKey(location, { splitActiveTerminal: true })) { return this.activeInstance; } return undefined; } private _getEditorOptions(location?: ITerminalLocationOptions): TerminalEditorLocation | undefined { - if (location && typeof location === 'object' && 'viewColumn' in location) { + if (location && typeof location === 'object' && hasKey(location, { viewColumn: true })) { // Terminal-specific workaround to resolve the active group in auxiliary windows to // override the locked editor behavior. if (location.viewColumn === ACTIVE_GROUP && isAuxiliaryWindow(getActiveWindow())) { @@ -1297,7 +1300,7 @@ class TerminalEditorStyle extends Themable { let uri = undefined; if (icon instanceof URI) { uri = icon; - } else if (icon instanceof Object && 'light' in icon && 'dark' in icon) { + } else if (icon instanceof Object && hasKey(icon, { light: true, dark: true })) { uri = isDark(colorTheme.type) ? icon.dark : icon.light; } const iconClasses = getUriClasses(instance, colorTheme.type); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 05cb619a218..9688d84234d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -644,9 +644,7 @@ class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop ( - isObject(e) && 'instanceId' in e - )) as ITerminalInstance[]; + const terminals = (dndData as unknown[]).filter(isTerminalInstance); if (terminals.length > 0) { originalEvent.dataTransfer.setData(TerminalDataTransfers.Terminals, JSON.stringify(terminals.map(e => e.resource.toString()))); } @@ -735,7 +733,7 @@ class TerminalTabsDragAndDrop extends Disposable implements IListDragAndDrop