diff --git a/news/1 Enhancements/17519.md b/news/1 Enhancements/17519.md new file mode 100644 index 000000000000..71a4d49110f4 --- /dev/null +++ b/news/1 Enhancements/17519.md @@ -0,0 +1 @@ +Declare limited support when running in virtual workspaces by only supporting language servers. diff --git a/package.json b/package.json index 700dd23424af..ceb16847f634 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "supported": false }, "virtualWorkspaces": { - "supported": false, - "description": "Limited support on the web." + "supported": "limited", + "description": "Only Partial IntelliSense supported." } }, "languageServerVersion": "0.5.30", diff --git a/src/client/activation/activationManager.ts b/src/client/activation/activationManager.ts index ade44d630004..20f51d33d57f 100644 --- a/src/client/activation/activationManager.ts +++ b/src/client/activation/activationManager.ts @@ -39,7 +39,16 @@ export class ExtensionActivationManager implements IExtensionActivationManager { @inject(IActiveResourceService) private readonly activeResourceService: IActiveResourceService, @inject(IExperimentService) private readonly experiments: IExperimentService, @inject(IInterpreterPathService) private readonly interpreterPathService: IInterpreterPathService, - ) {} + ) { + if (this.workspaceService.isVirtualWorkspace) { + this.activationServices = this.activationServices.filter( + (service) => service.supportedWorkspaceTypes.virtualWorkspace, + ); + this.singleActivationServices = this.singleActivationServices.filter( + (service) => service.supportedWorkspaceTypes.virtualWorkspace, + ); + } + } public dispose(): void { while (this.disposables.length > 0) { @@ -56,8 +65,9 @@ export class ExtensionActivationManager implements IExtensionActivationManager { await this.initialize(); // Activate all activation services together. + await Promise.all([ - Promise.all(this.singleActivationServices.map((item) => item.activate())), + ...this.singleActivationServices.map((item) => item.activate()), this.activateWorkspace(this.activeResourceService.getActiveResource()), ]); } diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index ef99d9780caa..d8189412c90b 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -49,6 +49,8 @@ export class LanguageServerExtensionActivationService private activatedServer?: IActivatedServer; + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + private readonly workspaceService: IWorkspaceService; private readonly configurationService: IConfigurationService; @@ -234,7 +236,6 @@ export class LanguageServerExtensionActivationService } this.sendTelemetryForChosenLanguageServer(serverType).ignoreErrors(); - await this.logStartup(serverType); let server = this.serviceContainer.get(ILanguageServerActivator, serverType); try { diff --git a/src/client/activation/common/analysisOptions.ts b/src/client/activation/common/analysisOptions.ts index 48433be0f5b1..f2839a25399d 100644 --- a/src/client/activation/common/analysisOptions.ts +++ b/src/client/activation/common/analysisOptions.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { Disposable, Event, EventEmitter, WorkspaceFolder } from 'vscode'; import { DocumentFilter, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient/node'; +import { IWorkspaceService } from '../../common/application/types'; import { PYTHON, PYTHON_LANGUAGE } from '../../common/constants'; import { IOutputChannel, Resource } from '../../common/types'; @@ -15,7 +16,10 @@ export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServ protected readonly didChange = new EventEmitter(); private readonly output: IOutputChannel; - protected constructor(lsOutputChannel: ILanguageServerOutputChannel) { + protected constructor( + lsOutputChannel: ILanguageServerOutputChannel, + protected readonly workspace: IWorkspaceService, + ) { this.output = lsOutputChannel.channel; } @@ -51,7 +55,7 @@ export abstract class LanguageServerAnalysisOptionsBase implements ILanguageServ } protected getDocumentFilters(_workspaceFolder?: WorkspaceFolder): DocumentFilter[] { - return PYTHON; + return this.workspace.isVirtualWorkspace ? [{ language: PYTHON_LANGUAGE }] : PYTHON; } protected async getInitializationOptions(): Promise { @@ -66,8 +70,9 @@ export abstract class LanguageServerAnalysisOptionsWithEnv extends LanguageServe protected constructor( private readonly envVarsProvider: IEnvironmentVariablesProvider, lsOutputChannel: ILanguageServerOutputChannel, + workspace: IWorkspaceService, ) { - super(lsOutputChannel); + super(lsOutputChannel, workspace); } public async initialize(_resource: Resource, _interpreter: PythonEnvironment | undefined) { diff --git a/src/client/activation/common/loadLanguageServerExtension.ts b/src/client/activation/common/loadLanguageServerExtension.ts index e30decac14b7..87fa5d9e6213 100644 --- a/src/client/activation/common/loadLanguageServerExtension.ts +++ b/src/client/activation/common/loadLanguageServerExtension.ts @@ -13,6 +13,8 @@ import { IExtensionSingleActivationService } from '../types'; @injectable() export class LoadLanguageServerExtension implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: true, virtualWorkspace: true }; + constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry, diff --git a/src/client/activation/extensionSurvey.ts b/src/client/activation/extensionSurvey.ts index 463f84e6e36c..885893e9f54e 100644 --- a/src/client/activation/extensionSurvey.ts +++ b/src/client/activation/extensionSurvey.ts @@ -28,6 +28,7 @@ const WAIT_TIME_TO_SHOW_SURVEY = 1000 * 60 * 60 * 3; // 3 hours @injectable() export class ExtensionSurveyPrompt implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IApplicationShell) private appShell: IApplicationShell, @inject(IBrowserService) private browserService: IBrowserService, diff --git a/src/client/activation/jedi/analysisOptions.ts b/src/client/activation/jedi/analysisOptions.ts index 396dbbfe8c7e..924e8b79eb6d 100644 --- a/src/client/activation/jedi/analysisOptions.ts +++ b/src/client/activation/jedi/analysisOptions.ts @@ -21,9 +21,9 @@ export class JediLanguageServerAnalysisOptions extends LanguageServerAnalysisOpt @inject(IEnvironmentVariablesProvider) envVarsProvider: IEnvironmentVariablesProvider, @inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, + @inject(IWorkspaceService) workspace: IWorkspaceService, ) { - super(envVarsProvider, lsOutputChannel); + super(envVarsProvider, lsOutputChannel, workspace); this.resource = undefined; } diff --git a/src/client/activation/node/analysisOptions.ts b/src/client/activation/node/analysisOptions.ts index c2aa885c8aac..2bde9f9cafbe 100644 --- a/src/client/activation/node/analysisOptions.ts +++ b/src/client/activation/node/analysisOptions.ts @@ -1,14 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import { IWorkspaceService } from '../../common/application/types'; import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions'; import { ILanguageServerOutputChannel } from '../types'; @injectable() export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase { - constructor(@inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel) { - super(lsOutputChannel); + constructor( + @inject(ILanguageServerOutputChannel) lsOutputChannel: ILanguageServerOutputChannel, + @inject(IWorkspaceService) workspace: IWorkspaceService, + ) { + super(lsOutputChannel, workspace); } protected async getInitializationOptions() { diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index d2f0d727d0cb..e90b8b8e88ee 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -58,6 +58,7 @@ export const IExtensionActivationService = Symbol('IExtensionActivationService') * @interface IExtensionActivationService */ export interface IExtensionActivationService { + supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean }; activate(resource: Resource): Promise; } @@ -178,5 +179,6 @@ export const IExtensionSingleActivationService = Symbol('IExtensionSingleActivat * @interface IExtensionSingleActivationService */ export interface IExtensionSingleActivationService { + supportedWorkspaceTypes: { untrustedWorkspace: boolean; virtualWorkspace: boolean }; activate(): Promise; } diff --git a/src/client/common/application/commands/reloadCommand.ts b/src/client/common/application/commands/reloadCommand.ts index 312cb674e7df..fa07f212c5db 100644 --- a/src/client/common/application/commands/reloadCommand.ts +++ b/src/client/common/application/commands/reloadCommand.ts @@ -14,6 +14,7 @@ import { IApplicationShell, ICommandManager } from '../types'; */ @injectable() export class ReloadVSCodeCommandHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IApplicationShell) private readonly appShell: IApplicationShell, diff --git a/src/client/common/application/commands/reportIssueCommand.ts b/src/client/common/application/commands/reportIssueCommand.ts index 5ba5bdbd50ba..18c95f669d78 100644 --- a/src/client/common/application/commands/reportIssueCommand.ts +++ b/src/client/common/application/commands/reportIssueCommand.ts @@ -22,6 +22,8 @@ import { EnvironmentType } from '../../../pythonEnvironments/info'; */ @injectable() export class ReportIssueCommandHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, diff --git a/src/client/common/application/debugSessionTelemetry.ts b/src/client/common/application/debugSessionTelemetry.ts index 5e4c67258988..2b1dd7f38917 100644 --- a/src/client/common/application/debugSessionTelemetry.ts +++ b/src/client/common/application/debugSessionTelemetry.ts @@ -62,6 +62,7 @@ class TelemetryTracker implements DebugAdapterTracker { @injectable() export class DebugSessionTelemetry implements DebugAdapterTrackerFactory, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry, @inject(IDebugService) debugService: IDebugService, diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index eac770efafa6..ec5c7dcf5cab 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -729,7 +729,10 @@ export interface IWorkspaceService { * @memberof IWorkspaceService */ readonly hasWorkspaceFolders: boolean; - + /** + * Returns if we're running in a virtual workspace. + */ + readonly isVirtualWorkspace: boolean; /** * Returns the [workspace folder](#WorkspaceFolder) that contains a given uri. * * returns `undefined` when the given uri doesn't match any workspace folder diff --git a/src/client/common/application/workspace.ts b/src/client/common/application/workspace.ts index f3eb57c6156c..5ef4870a8808 100644 --- a/src/client/common/application/workspace.ts +++ b/src/client/common/application/workspace.ts @@ -83,6 +83,12 @@ export class WorkspaceService implements IWorkspaceService { : defaultValue; } + public get isVirtualWorkspace(): boolean { + const isVirtualWorkspace = + workspace.workspaceFolders && workspace.workspaceFolders.every((f) => f.uri.scheme !== 'file'); + return !!isVirtualWorkspace; + } + private get searchExcludes() { const searchExcludes = this.getConfiguration('search.exclude'); const enabledSearchExcludes = Object.keys(searchExcludes).filter((key) => searchExcludes.get(key) === true); diff --git a/src/client/common/insidersBuild/insidersExtensionService.ts b/src/client/common/insidersBuild/insidersExtensionService.ts index 6a4d01f2c8e9..4d9d91d4a1ca 100644 --- a/src/client/common/insidersBuild/insidersExtensionService.ts +++ b/src/client/common/insidersBuild/insidersExtensionService.ts @@ -19,6 +19,7 @@ import { traceDecoratorError } from '../../logging'; @injectable() export class InsidersExtensionService implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IExtensionChannelService) private readonly extensionChannelService: IExtensionChannelService, @inject(IInsiderExtensionPrompt) private readonly insidersPrompt: IInsiderExtensionPrompt, diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index 188a9b42c1d6..c72e3ab59e21 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -63,6 +63,7 @@ export type KeysStorage = { key: string; defaultValue: unknown }; @injectable() export class PersistentStateFactory implements IPersistentStateFactory, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; public readonly _globalKeysStorage = new PersistentState( this.globalState, GLOBAL_PERSISTENT_KEYS, diff --git a/src/client/debugger/extension/adapter/activator.ts b/src/client/debugger/extension/adapter/activator.ts index c7618c7ee146..e46d673ea60c 100644 --- a/src/client/debugger/extension/adapter/activator.ts +++ b/src/client/debugger/extension/adapter/activator.ts @@ -13,6 +13,7 @@ import { IDebugAdapterDescriptorFactory, IDebugSessionLoggingFactory, IOutdatedD @injectable() export class DebugAdapterActivator implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(IDebugService) private readonly debugService: IDebugService, @inject(IDebugAdapterDescriptorFactory) private descriptorFactory: IDebugAdapterDescriptorFactory, diff --git a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts b/src/client/debugger/extension/configuration/launch.json/completionProvider.ts index 73f062184670..da6595c5bb2a 100644 --- a/src/client/debugger/extension/configuration/launch.json/completionProvider.ts +++ b/src/client/debugger/extension/configuration/launch.json/completionProvider.ts @@ -28,6 +28,7 @@ enum JsonLanguages { @injectable() export class LaunchJsonCompletionProvider implements CompletionItemProvider, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(ILanguageService) private readonly languageService: ILanguageService, @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, diff --git a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts b/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts index d74d7779c8ff..1c0354dd23f5 100644 --- a/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts +++ b/src/client/debugger/extension/configuration/launch.json/interpreterPathCommand.ts @@ -12,6 +12,7 @@ import { IConfigurationService, IDisposable, IDisposableRegistry } from '../../. @injectable() export class InterpreterPathCommand implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IConfigurationService) private readonly configurationService: IConfigurationService, diff --git a/src/client/debugger/extension/configuration/launch.json/updaterService.ts b/src/client/debugger/extension/configuration/launch.json/updaterService.ts index bd91ca2f20c0..232d068cc47b 100644 --- a/src/client/debugger/extension/configuration/launch.json/updaterService.ts +++ b/src/client/debugger/extension/configuration/launch.json/updaterService.ts @@ -146,6 +146,7 @@ export class LaunchJsonUpdaterServiceHelper { @injectable() export class LaunchJsonUpdaterService implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry, diff --git a/src/client/debugger/extension/debugCommands.ts b/src/client/debugger/extension/debugCommands.ts index 5d0e3ab7fbcc..071f246879ca 100644 --- a/src/client/debugger/extension/debugCommands.ts +++ b/src/client/debugger/extension/debugCommands.ts @@ -15,6 +15,8 @@ import { DebugPurpose, LaunchRequestArguments } from '../types'; @injectable() export class DebugCommands implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IDebugService) private readonly debugService: IDebugService, diff --git a/src/client/extension.ts b/src/client/extension.ts index 500a8ecd826a..c04dfb3c3c98 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -68,6 +68,7 @@ export async function activate(context: IExtensionContext): Promise { if (activatedServiceContainer) { - const interpreterManager = activatedServiceContainer.get(IInterpreterService); const workspaceService = activatedServiceContainer.get(IWorkspaceService); + const interpreterManager = activatedServiceContainer.get(IInterpreterService); const workspaces = workspaceService.workspaceFolders ?? []; await interpreterManager .refresh(workspaces.length > 0 ? workspaces[0].uri : undefined) @@ -159,6 +160,7 @@ async function handleError(ex: Error, startupDurations: IStartupDurations) { "Extension activation failed, run the 'Developer: Toggle Developer Tools' command for more information.", ); traceError('extension activation failed', ex); + await sendErrorTelemetry(ex, startupDurations, activatedServiceContainer); } diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 52abe2d840e4..fae6c717e5ff 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -9,16 +9,13 @@ import { registerTypes as activationRegisterTypes } from './activation/serviceRe import { IExtensionActivationManager } from './activation/types'; import { registerTypes as appRegisterTypes } from './application/serviceRegistry'; import { IApplicationDiagnostics } from './application/types'; -import { DebugService } from './common/application/debugService'; -import { IApplicationEnvironment, ICommandManager } from './common/application/types'; +import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from './common/application/types'; import { Commands, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL, UseProposedApi } from './common/constants'; import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { IFileSystem } from './common/platform/types'; import { IConfigurationService, IDisposableRegistry, IExtensions, IOutputChannel } from './common/types'; import { noop } from './common/utils/misc'; import { DebuggerTypeName } from './debugger/constants'; -import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; -import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry'; import { IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; @@ -47,6 +44,9 @@ import { ActivationResult, ExtensionState } from './components'; import { Components } from './extensionInit'; import { setDefaultLanguageServer } from './activation/common/defaultlanguageServer'; import { getLoggingLevel } from './logging/settings'; +import { DebugService } from './common/application/debugService'; +import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher'; +import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types'; export async function activateComponents( // `ext` is passed to any extra activation funcs. @@ -117,8 +117,9 @@ async function activateLegacy(ext: ExtensionState): Promise { // directly queries VSCode API. setLoggingLevel(getLoggingLevel()); - // `IConfigurationService` may depend any of the registered types, so doing it after all registrations are finished. const configuration = serviceManager.get(IConfigurationService); + // Settings are dependent on Experiment service, so we need to initialize it after experiments are activated. + serviceContainer.get(IConfigurationService).getSettings().initialize(); const languageServerType = configuration.getSettings().languageServer; // Language feature registrations. @@ -128,66 +129,68 @@ async function activateLegacy(ext: ExtensionState): Promise { // "initialize" "services" + const disposables = serviceManager.get(IDisposableRegistry); + const workspaceService = serviceContainer.get(IWorkspaceService); + const cmdManager = serviceContainer.get(ICommandManager); + languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); const interpreterManager = serviceContainer.get(IInterpreterService); interpreterManager.initialize(); + if (!workspaceService.isVirtualWorkspace) { + const handlers = serviceManager.getAll(IDebugSessionEventHandlers); + const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); + dispatcher.registerEventHandlers(); - const handlers = serviceManager.getAll(IDebugSessionEventHandlers); - const disposables = serviceManager.get(IDisposableRegistry); - const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables); - dispatcher.registerEventHandlers(); + const outputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); + cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); - const cmdManager = serviceContainer.get(ICommandManager); - const outputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show())); - cmdManager.executeCommand('setContext', 'python.vscode.channel', applicationEnv.channel).then(noop, noop); + serviceContainer.get(IApplicationDiagnostics).register(); - serviceContainer.get(IApplicationDiagnostics).register(); + serviceManager.get(ITerminalAutoActivation).register(); + const pythonSettings = configuration.getSettings(); - // "activate" everything else + const sortImports = serviceContainer.get(ISortImportsEditingProvider); + sortImports.registerCommands(); - const manager = serviceContainer.get(IExtensionActivationManager); - context.subscriptions.push(manager); + serviceManager.get(ICodeExecutionManager).registerCommands(); - // Settings are dependent on Experiment service, so we need to initialize it after experiments are activated. - serviceContainer.get(IConfigurationService).getSettings().initialize(); + context.subscriptions.push(new LinterCommands(serviceManager)); - const activationPromise = manager.activate(); - - serviceManager.get(ITerminalAutoActivation).register(); - const pythonSettings = configuration.getSettings(); + if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') { + const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); + context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + } - const sortImports = serviceContainer.get(ISortImportsEditingProvider); - sortImports.registerCommands(); + context.subscriptions.push(new ReplProvider(serviceContainer)); - serviceManager.get(ICodeExecutionManager).registerCommands(); + const terminalProvider = new TerminalProvider(serviceContainer); + terminalProvider.initialize(window.activeTerminal).ignoreErrors(); + context.subscriptions.push(terminalProvider); - context.subscriptions.push(new LinterCommands(serviceManager)); + context.subscriptions.push( + languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { + providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports], + }), + ); - languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration()); + serviceContainer + .getAll(IDebugConfigurationService) + .forEach((debugConfigProvider) => { + context.subscriptions.push( + debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider), + ); + }); - if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') { - const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + serviceContainer.get(IDebuggerBanner).initialize(); } - context.subscriptions.push(new ReplProvider(serviceContainer)); - - const terminalProvider = new TerminalProvider(serviceContainer); - terminalProvider.initialize(window.activeTerminal).ignoreErrors(); - context.subscriptions.push(terminalProvider); - - context.subscriptions.push( - languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { - providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports], - }), - ); + // "activate" everything else - serviceContainer.getAll(IDebugConfigurationService).forEach((debugConfigProvider) => { - context.subscriptions.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider)); - }); + const manager = serviceContainer.get(IExtensionActivationManager); + context.subscriptions.push(manager); - serviceContainer.get(IDebuggerBanner).initialize(); + const activationPromise = manager.activate(); return { fullyReady: activationPromise }; } diff --git a/src/client/extensionInit.ts b/src/client/extensionInit.ts index 1a8bba2e7c76..b481325c69d0 100644 --- a/src/client/extensionInit.ts +++ b/src/client/extensionInit.ts @@ -5,6 +5,7 @@ import { Container } from 'inversify'; import { Disposable, Memento, OutputChannel, window } from 'vscode'; +import { instance, mock } from 'ts-mockito'; import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; @@ -28,6 +29,7 @@ import { TEST_OUTPUT_CHANNEL } from './testing/constants'; import { IDiscoveryAPI } from './pythonEnvironments/base/locator'; import { registerLogger } from './logging'; import { OutputChannelLogger } from './logging/outputChannelLogger'; +import { WorkspaceService } from './common/application/workspace'; // The code in this module should do nothing more complex than register // objects to DI and simple init (e.g. no side effects). That implies @@ -54,7 +56,11 @@ export function initializeGlobals( const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python()); context.subscriptions.push(registerLogger(new OutputChannelLogger(standardOutputChannel))); - const unitTestOutChannel = window.createOutputChannel(OutputChannelNames.pythonTest()); + const workspaceService = new WorkspaceService(); + const unitTestOutChannel = workspaceService.isVirtualWorkspace + ? // Do not create any test related output UI when using virtual workspaces. + instance(mock()) + : window.createOutputChannel(OutputChannelNames.pythonTest()); serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); diff --git a/src/client/interpreter/configuration/interpreterSelector/commands/base.ts b/src/client/interpreter/configuration/interpreterSelector/commands/base.ts index 618f698df35a..7ecafae3a04c 100644 --- a/src/client/interpreter/configuration/interpreterSelector/commands/base.ts +++ b/src/client/interpreter/configuration/interpreterSelector/commands/base.ts @@ -14,6 +14,7 @@ import { IPythonPathUpdaterServiceManager } from '../../types'; @injectable() export abstract class BaseInterpreterSelectorCommand implements IExtensionSingleActivationService, IDisposable { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; protected disposables: Disposable[] = []; constructor( @unmanaged() protected readonly pythonPathUpdaterService: IPythonPathUpdaterServiceManager, diff --git a/src/client/interpreter/display/progressDisplay.ts b/src/client/interpreter/display/progressDisplay.ts index d273b94801ac..d641ae70d2c7 100644 --- a/src/client/interpreter/display/progressDisplay.ts +++ b/src/client/interpreter/display/progressDisplay.ts @@ -16,6 +16,8 @@ import { IComponentAdapter } from '../contracts'; // The parts of IComponentAdapter used here. @injectable() export class InterpreterLocatorProgressStatubarHandler implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + private deferred: Deferred | undefined; private isFirstTimeLoadingInterpreters = true; diff --git a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts index 09f1ea32f10a..c842d5b2c45e 100644 --- a/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts +++ b/src/client/interpreter/virtualEnvs/condaInheritEnvPrompt.ts @@ -18,6 +18,7 @@ export const condaInheritEnvPromptKey = 'CONDA_INHERIT_ENV_PROMPT_KEY'; @injectable() export class CondaInheritEnvPrompt implements IExtensionActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; constructor( @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, diff --git a/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts b/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts index 0f8223647c0a..914a639e0314 100644 --- a/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts +++ b/src/client/interpreter/virtualEnvs/virtualEnvPrompt.ts @@ -18,6 +18,8 @@ import { IComponentAdapter, IInterpreterHelper } from '../contracts'; const doNotDisplayPromptStateKey = 'MESSAGE_KEY_FOR_VIRTUAL_ENV'; @injectable() export class VirtualEnvironmentPrompt implements IExtensionActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + constructor( @inject(IPersistentStateFactory) private readonly persistentStateFactory: IPersistentStateFactory, @inject(IInterpreterHelper) private readonly helper: IInterpreterHelper, diff --git a/src/client/providers/codeActionProvider/main.ts b/src/client/providers/codeActionProvider/main.ts index b543d3a8baa5..5e3b502a4a03 100644 --- a/src/client/providers/codeActionProvider/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -9,6 +9,7 @@ import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() export class CodeActionProviderService implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {} public async activate(): Promise { const vscode = require('vscode') as typeof vscodeTypes; diff --git a/src/client/providers/linterProvider.ts b/src/client/providers/linterProvider.ts index 8be6be069154..a694e0a891ad 100644 --- a/src/client/providers/linterProvider.ts +++ b/src/client/providers/linterProvider.ts @@ -18,6 +18,7 @@ import { ILinterManager, ILintingEngine } from '../linters/types'; @injectable() export class LinterProvider implements IExtensionActivationService, Disposable { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; private interpreterService: IInterpreterService; private documents: IDocumentManager; private configuration: IConfigurationService; diff --git a/src/client/telemetry/importTracker.ts b/src/client/telemetry/importTracker.ts index defa79b1ac02..39f278bef9bb 100644 --- a/src/client/telemetry/importTracker.ts +++ b/src/client/telemetry/importTracker.ts @@ -47,6 +47,8 @@ const testExecution = isTestExecution(); @injectable() export class ImportTracker implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + private pendingChecks = new Map(); private static sentMatches: Set = new Set(); diff --git a/src/client/tensorBoard/nbextensionCodeLensProvider.ts b/src/client/tensorBoard/nbextensionCodeLensProvider.ts index 1c477ab4a70a..986e1c74457f 100644 --- a/src/client/tensorBoard/nbextensionCodeLensProvider.ts +++ b/src/client/tensorBoard/nbextensionCodeLensProvider.ts @@ -16,6 +16,8 @@ import { containsNotebookExtension } from './helpers'; @injectable() export class TensorBoardNbextensionCodeLensProvider implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private sendTelemetryOnce = once( sendTelemetryEvent.bind(this, EventName.TENSORBOARD_ENTRYPOINT_SHOWN, undefined, { trigger: TensorBoardEntrypointTrigger.nbextension, diff --git a/src/client/tensorBoard/tensorBoardFileWatcher.ts b/src/client/tensorBoard/tensorBoardFileWatcher.ts index 4070e481fec3..e35a021db321 100644 --- a/src/client/tensorBoard/tensorBoardFileWatcher.ts +++ b/src/client/tensorBoard/tensorBoardFileWatcher.ts @@ -13,6 +13,8 @@ import { TensorBoardPrompt } from './tensorBoardPrompt'; @injectable() export class TensorBoardFileWatcher implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private fileSystemWatchers = new Map(); private globPatterns = ['*tfevents*', '*/*tfevents*', '*/*/*tfevents*']; diff --git a/src/client/tensorBoard/tensorBoardImportCodeLensProvider.ts b/src/client/tensorBoard/tensorBoardImportCodeLensProvider.ts index 81324feba6eb..6ad000dfdd7d 100644 --- a/src/client/tensorBoard/tensorBoardImportCodeLensProvider.ts +++ b/src/client/tensorBoard/tensorBoardImportCodeLensProvider.ts @@ -16,6 +16,8 @@ import { containsTensorBoardImport } from './helpers'; @injectable() export class TensorBoardImportCodeLensProvider implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private sendTelemetryOnce = once( sendTelemetryEvent.bind(this, EventName.TENSORBOARD_ENTRYPOINT_SHOWN, undefined, { trigger: TensorBoardEntrypointTrigger.fileimport, diff --git a/src/client/tensorBoard/tensorBoardSessionProvider.ts b/src/client/tensorBoard/tensorBoardSessionProvider.ts index 348c2910271d..bc4b9d1130e8 100644 --- a/src/client/tensorBoard/tensorBoardSessionProvider.ts +++ b/src/client/tensorBoard/tensorBoardSessionProvider.ts @@ -29,6 +29,8 @@ const PREFERRED_VIEWGROUP = 'PythonTensorBoardWebviewPreferredViewGroup'; @injectable() export class TensorBoardSessionProvider implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private knownSessions: TensorBoardSession[] = []; private preferredViewGroupMemento: IPersistentState; diff --git a/src/client/tensorBoard/tensorBoardUsageTracker.ts b/src/client/tensorBoard/tensorBoardUsageTracker.ts index 90f6ef7e9b32..423be8868e30 100644 --- a/src/client/tensorBoard/tensorBoardUsageTracker.ts +++ b/src/client/tensorBoard/tensorBoardUsageTracker.ts @@ -20,6 +20,8 @@ const testExecution = isTestExecution(); // contains a valid TensorBoard import. @injectable() export class TensorBoardUsageTracker implements IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + constructor( @inject(IDocumentManager) private documentManager: IDocumentManager, @inject(IDisposableRegistry) private disposables: IDisposableRegistry, diff --git a/src/client/tensorBoard/terminalWatcher.ts b/src/client/tensorBoard/terminalWatcher.ts index 22412bfc0f9e..5aadc12dc4c0 100644 --- a/src/client/tensorBoard/terminalWatcher.ts +++ b/src/client/tensorBoard/terminalWatcher.ts @@ -8,6 +8,8 @@ import { EventName } from '../telemetry/constants'; // Every 5 min look, through active terminals to see if any are running `tensorboard` @injectable() export class TerminalWatcher implements IExtensionSingleActivationService, IDisposable { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private handle: NodeJS.Timeout | undefined; constructor(@inject(IDisposableRegistry) private disposables: IDisposableRegistry) {} diff --git a/src/client/testing/common/testConfigurationManager.ts b/src/client/testing/common/testConfigurationManager.ts index 391a26bd4ebf..c2b050cb524a 100644 --- a/src/client/testing/common/testConfigurationManager.ts +++ b/src/client/testing/common/testConfigurationManager.ts @@ -1,12 +1,11 @@ import * as path from 'path'; -import { OutputChannel, QuickPickItem, QuickPickOptions, Uri } from 'vscode'; +import { QuickPickItem, QuickPickOptions, Uri } from 'vscode'; import { IApplicationShell } from '../../common/application/types'; import { IFileSystem } from '../../common/platform/types'; -import { IInstaller, IOutputChannel } from '../../common/types'; +import { IInstaller } from '../../common/types'; import { createDeferred } from '../../common/utils/async'; import { IServiceContainer } from '../../ioc/types'; import { traceInfo } from '../../logging'; -import { TEST_OUTPUT_CHANNEL } from '../constants'; import { UNIT_TEST_PRODUCTS } from './constants'; import { ITestConfigSettingsService, ITestConfigurationManager, UnitTestProduct } from './types'; @@ -16,8 +15,6 @@ function handleCancelled(): void { } export abstract class TestConfigurationManager implements ITestConfigurationManager { - protected readonly outputChannel: OutputChannel; - protected readonly installer: IInstaller; protected readonly testConfigSettingsService: ITestConfigSettingsService; @@ -30,7 +27,6 @@ export abstract class TestConfigurationManager implements ITestConfigurationMana protected readonly serviceContainer: IServiceContainer, cfg?: ITestConfigSettingsService, ) { - this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); this.installer = serviceContainer.get(IInstaller); this.testConfigSettingsService = cfg || serviceContainer.get(ITestConfigSettingsService); diff --git a/src/client/testing/common/updateTestSettings.ts b/src/client/testing/common/updateTestSettings.ts index 0fb65d56e3a3..c7dd5b182373 100644 --- a/src/client/testing/common/updateTestSettings.ts +++ b/src/client/testing/common/updateTestSettings.ts @@ -17,6 +17,7 @@ import { traceDecoratorError, traceError } from '../../logging'; // TODO: rename the class since it is not used just for test settings @injectable() export class UpdateTestSettingService implements IExtensionActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; constructor( @inject(IFileSystem) private readonly fs: IFileSystem, @inject(IApplicationEnvironment) private readonly application: IApplicationEnvironment, diff --git a/src/client/testing/main.ts b/src/client/testing/main.ts index bc4d69412a4e..482538a38789 100644 --- a/src/client/testing/main.ts +++ b/src/client/testing/main.ts @@ -35,6 +35,7 @@ export class TestingService implements ITestingService { @injectable() export class UnitTestManagementService implements IExtensionActivationService { private activatedOnce: boolean = false; + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; private readonly disposableRegistry: Disposable[]; private workspaceService: IWorkspaceService; private context: IContextKeyManager; diff --git a/src/client/testing/testController/controller.ts b/src/client/testing/testController/controller.ts index af0f56106a1b..91d0f4427a10 100644 --- a/src/client/testing/testController/controller.ts +++ b/src/client/testing/testController/controller.ts @@ -16,6 +16,7 @@ import { Uri, EventEmitter, } from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; import { IWorkspaceService } from '../../common/application/types'; import { IConfigurationService, IDisposableRegistry, Resource } from '../../common/types'; import { DelayedTrigger, IDelayedTrigger } from '../../common/utils/delayTrigger'; @@ -27,7 +28,9 @@ import { DebugTestTag, getNodeByUri, RunTestTag } from './common/testItemUtiliti import { ITestController, ITestFrameworkController, TestRefreshOptions } from './common/types'; @injectable() -export class PythonTestController implements ITestController { +export class PythonTestController implements ITestController, IExtensionSingleActivationService { + public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: false }; + private readonly testController: TestController; private readonly refreshData: IDelayedTrigger; @@ -90,7 +93,9 @@ export class PythonTestController implements ITestController { ), ); this.testController.resolveHandler = this.resolveChildren.bind(this); + } + public async activate(): Promise { this.watchForTestChanges(); } diff --git a/src/client/testing/testController/serviceRegistry.ts b/src/client/testing/testController/serviceRegistry.ts index 0e8320a4f153..840eb14b1f27 100644 --- a/src/client/testing/testController/serviceRegistry.ts +++ b/src/client/testing/testController/serviceRegistry.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { IExtensionSingleActivationService } from '../../activation/types'; import { IServiceManager } from '../../ioc/types'; import { PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../common/constants'; import { TestDiscoveryHelper } from './common/discoveryHelper'; @@ -24,4 +25,5 @@ export function registerTestControllerTypes(serviceManager: IServiceManager): vo ); serviceManager.addSingleton(ITestsRunner, UnittestRunner, UNITTEST_PROVIDER); serviceManager.addSingleton(ITestController, PythonTestController); + serviceManager.addBinding(ITestController, IExtensionSingleActivationService); } diff --git a/src/test/activation/node/analysisOptions.unit.test.ts b/src/test/activation/node/analysisOptions.unit.test.ts index 628282f051e0..3e14130c650e 100644 --- a/src/test/activation/node/analysisOptions.unit.test.ts +++ b/src/test/activation/node/analysisOptions.unit.test.ts @@ -1,13 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import * as typemoq from 'typemoq'; import { WorkspaceFolder } from 'vscode'; import { DocumentFilter } from 'vscode-languageclient/node'; import { NodeLanguageServerAnalysisOptions } from '../../../client/activation/node/analysisOptions'; import { ILanguageServerOutputChannel } from '../../../client/activation/types'; -import { PYTHON } from '../../../client/common/constants'; +import { IWorkspaceService } from '../../../client/common/application/types'; +import { PYTHON, PYTHON_LANGUAGE } from '../../../client/common/constants'; import { IOutputChannel } from '../../../client/common/types'; suite('Pylance Language Server - Analysis Options', () => { @@ -29,12 +30,15 @@ suite('Pylance Language Server - Analysis Options', () => { let analysisOptions: TestClass; let outputChannel: IOutputChannel; let lsOutputChannel: typemoq.IMock; + let workspace: typemoq.IMock; setup(() => { outputChannel = typemoq.Mock.ofType().object; + workspace = typemoq.Mock.ofType(); + workspace.setup((w) => w.isVirtualWorkspace).returns(() => false); lsOutputChannel = typemoq.Mock.ofType(); lsOutputChannel.setup((l) => l.channel).returns(() => outputChannel); - analysisOptions = new TestClass(lsOutputChannel.object); + analysisOptions = new TestClass(lsOutputChannel.object, workspace.object); }); test('Workspace folder is undefined', () => { @@ -42,11 +46,18 @@ suite('Pylance Language Server - Analysis Options', () => { expect(workspaceFolder).to.be.equal(undefined); }); - test('Document filter matches all python', () => { + test('Document filter matches expected python language schemes', () => { const filter = analysisOptions.getDocumentFilters(); expect(filter).to.be.equal(PYTHON); }); + test('Document filter matches all python language schemes when in virtual workspace', () => { + workspace.reset(); + workspace.setup((w) => w.isVirtualWorkspace).returns(() => true); + const filter = analysisOptions.getDocumentFilters(); + assert.deepEqual(filter, [{ language: PYTHON_LANGUAGE }]); + }); + test('Initialization options include experimentation capability', async () => { const options = await analysisOptions.getInitializationOptions(); expect(options?.experimentationSupport).to.be.equal(true);