Skip to content

Commit 8267781

Browse files
author
Eric Snow
authored
Clean up the extension activation code. (#10455)
(for #10454) This is the basic prep work for making the flow of extension activation more clear.
1 parent 3a0fa48 commit 8267781

File tree

8 files changed

+574
-445
lines changed

8 files changed

+574
-445
lines changed

news/3 Code Health/10454.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactor the extension activation code to split on phases.

src/client/api.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
'use strict';
55

6+
import { isTestExecution } from './common/constants';
67
import { DebugAdapterNewPtvsd } from './common/experimentGroups';
78
import { traceError } from './common/logger';
89
import { IExperimentsManager } from './common/types';
910
import { RemoteDebuggerExternalLauncherScriptProvider } from './debugger/debugAdapter/DebugClients/launcherProvider';
1011
import { IDebugAdapterDescriptorFactory } from './debugger/extension/types';
12+
import { IServiceContainer, IServiceManager } from './ioc/types';
1113

1214
/*
1315
* Do not introduce any breaking changes to this API.
@@ -38,10 +40,13 @@ export interface IExtensionApi {
3840
export function buildApi(
3941
// tslint:disable-next-line:no-any
4042
ready: Promise<any>,
41-
experimentsManager: IExperimentsManager,
42-
debugFactory: IDebugAdapterDescriptorFactory
43+
serviceManager: IServiceManager,
44+
serviceContainer: IServiceContainer
4345
) {
44-
return {
46+
const experimentsManager = serviceContainer.get<IExperimentsManager>(IExperimentsManager);
47+
const debugFactory = serviceContainer.get<IDebugAdapterDescriptorFactory>(IDebugAdapterDescriptorFactory);
48+
49+
const api = {
4550
// 'ready' will propagate the exception, but we must log it here first.
4651
ready: ready.catch(ex => {
4752
traceError('Failure during activation.', ex);
@@ -69,4 +74,13 @@ export function buildApi(
6974
}
7075
}
7176
};
77+
78+
// In test environment return the DI Container.
79+
if (isTestExecution()) {
80+
// tslint:disable:no-any
81+
(api as any).serviceContainer = serviceContainer;
82+
(api as any).serviceManager = serviceManager;
83+
// tslint:enable:no-any
84+
}
85+
return api;
7286
}

src/client/extension.ts

Lines changed: 69 additions & 369 deletions
Large diffs are not rendered by default.

src/client/extensionActivation.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable:max-func-body-length
7+
8+
import { CodeActionKind, debug, DebugConfigurationProvider, languages, OutputChannel, window } from 'vscode';
9+
10+
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
11+
import { IExtensionActivationManager, ILanguageServerExtension } from './activation/types';
12+
import { registerTypes as appRegisterTypes } from './application/serviceRegistry';
13+
import { IApplicationDiagnostics } from './application/types';
14+
import { DebugService } from './common/application/debugService';
15+
import { ICommandManager, IWorkspaceService } from './common/application/types';
16+
import { Commands, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants';
17+
import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry';
18+
import { traceError } from './common/logger';
19+
import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry';
20+
import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry';
21+
import { registerTypes as commonRegisterTypes } from './common/serviceRegistry';
22+
import {
23+
IConfigurationService,
24+
IDisposableRegistry,
25+
IExperimentsManager,
26+
IExtensionContext,
27+
IFeatureDeprecationManager,
28+
IOutputChannel
29+
} from './common/types';
30+
import { OutputChannelNames } from './common/utils/localize';
31+
import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry';
32+
import { JUPYTER_OUTPUT_CHANNEL } from './datascience/constants';
33+
import { registerTypes as dataScienceRegisterTypes } from './datascience/serviceRegistry';
34+
import { IDataScience } from './datascience/types';
35+
import { DebuggerTypeName } from './debugger/constants';
36+
import { DebugSessionEventDispatcher } from './debugger/extension/hooks/eventHandlerDispatcher';
37+
import { IDebugSessionEventHandlers } from './debugger/extension/hooks/types';
38+
import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry';
39+
import { IDebugConfigurationService, IDebuggerBanner } from './debugger/extension/types';
40+
import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry';
41+
import { IInterpreterSelector } from './interpreter/configuration/types';
42+
import {
43+
IInterpreterLocatorProgressHandler,
44+
IInterpreterLocatorProgressService,
45+
IInterpreterService
46+
} from './interpreter/contracts';
47+
import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry';
48+
import { IServiceContainer, IServiceManager } from './ioc/types';
49+
import { getLanguageConfiguration } from './language/languageConfiguration';
50+
import { LinterCommands } from './linters/linterCommands';
51+
import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
52+
import { PythonCodeActionProvider } from './providers/codeActionProvider/pythonCodeActionProvider';
53+
import { PythonFormattingEditProvider } from './providers/formatProvider';
54+
import { ReplProvider } from './providers/replProvider';
55+
import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry';
56+
import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider';
57+
import { TerminalProvider } from './providers/terminalProvider';
58+
import { ISortImportsEditingProvider } from './providers/types';
59+
import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry';
60+
import { ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types';
61+
import { TEST_OUTPUT_CHANNEL } from './testing/common/constants';
62+
import { ITestContextService } from './testing/common/types';
63+
import { ITestCodeNavigatorCommandHandler, ITestExplorerCommandHandler } from './testing/navigation/types';
64+
import { registerTypes as unitTestsRegisterTypes } from './testing/serviceRegistry';
65+
66+
export async function activateComponents(
67+
context: IExtensionContext,
68+
serviceManager: IServiceManager,
69+
serviceContainer: IServiceContainer
70+
) {
71+
// We will be pulling code over from activateLegacy().
72+
73+
return activateLegacy(context, serviceManager, serviceContainer);
74+
}
75+
76+
/////////////////////////////
77+
// old activation code
78+
79+
// tslint:disable-next-line:no-suspicious-comment
80+
// TODO(GH-10454): Gradually move simple initialization
81+
// and DI registration currently in this function over
82+
// to initializeComponents(). Likewise with complex
83+
// init and activation: move them to activateComponents().
84+
85+
async function activateLegacy(
86+
context: IExtensionContext,
87+
serviceManager: IServiceManager,
88+
serviceContainer: IServiceContainer
89+
) {
90+
// register "services"
91+
92+
const standardOutputChannel = window.createOutputChannel(OutputChannelNames.python());
93+
const unitTestOutChannel = window.createOutputChannel(OutputChannelNames.pythonTest());
94+
const jupyterOutputChannel = window.createOutputChannel(OutputChannelNames.jupyter());
95+
serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL);
96+
serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL);
97+
serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, jupyterOutputChannel, JUPYTER_OUTPUT_CHANNEL);
98+
99+
commonRegisterTypes(serviceManager);
100+
processRegisterTypes(serviceManager);
101+
variableRegisterTypes(serviceManager);
102+
unitTestsRegisterTypes(serviceManager);
103+
lintersRegisterTypes(serviceManager);
104+
interpretersRegisterTypes(serviceManager);
105+
formattersRegisterTypes(serviceManager);
106+
platformRegisterTypes(serviceManager);
107+
installerRegisterTypes(serviceManager);
108+
commonRegisterTerminalTypes(serviceManager);
109+
dataScienceRegisterTypes(serviceManager);
110+
debugConfigurationRegisterTypes(serviceManager);
111+
112+
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
113+
const languageServerType = configuration.getSettings().languageServer;
114+
115+
appRegisterTypes(serviceManager, languageServerType);
116+
providersRegisterTypes(serviceManager);
117+
activationRegisterTypes(serviceManager, languageServerType);
118+
119+
// "initialize" "services"
120+
121+
const abExperiments = serviceContainer.get<IExperimentsManager>(IExperimentsManager);
122+
await abExperiments.activate();
123+
const selector = serviceContainer.get<IInterpreterSelector>(IInterpreterSelector);
124+
selector.initialize();
125+
context.subscriptions.push(selector);
126+
127+
const interpreterManager = serviceContainer.get<IInterpreterService>(IInterpreterService);
128+
interpreterManager.initialize();
129+
130+
const handlers = serviceManager.getAll<IDebugSessionEventHandlers>(IDebugSessionEventHandlers);
131+
const disposables = serviceManager.get<IDisposableRegistry>(IDisposableRegistry);
132+
const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables);
133+
dispatcher.registerEventHandlers();
134+
135+
const cmdManager = serviceContainer.get<ICommandManager>(ICommandManager);
136+
const outputChannel = serviceManager.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
137+
disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show()));
138+
139+
// Display progress of interpreter refreshes only after extension has activated.
140+
serviceContainer.get<IInterpreterLocatorProgressHandler>(IInterpreterLocatorProgressHandler).register();
141+
serviceContainer.get<IInterpreterLocatorProgressService>(IInterpreterLocatorProgressService).register();
142+
serviceContainer.get<IApplicationDiagnostics>(IApplicationDiagnostics).register();
143+
serviceContainer.get<ITestCodeNavigatorCommandHandler>(ITestCodeNavigatorCommandHandler).register();
144+
serviceContainer.get<ITestExplorerCommandHandler>(ITestExplorerCommandHandler).register();
145+
serviceContainer.get<ILanguageServerExtension>(ILanguageServerExtension).register();
146+
serviceContainer.get<ITestContextService>(ITestContextService).register();
147+
148+
// "activate" everything else
149+
150+
const manager = serviceContainer.get<IExtensionActivationManager>(IExtensionActivationManager);
151+
context.subscriptions.push(manager);
152+
const activationPromise = manager.activate();
153+
154+
serviceManager.get<ITerminalAutoActivation>(ITerminalAutoActivation).register();
155+
const pythonSettings = configuration.getSettings();
156+
157+
activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceContainer);
158+
159+
const sortImports = serviceContainer.get<ISortImportsEditingProvider>(ISortImportsEditingProvider);
160+
sortImports.registerCommands();
161+
162+
serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();
163+
164+
const workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
165+
interpreterManager
166+
.refresh(workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders![0].uri : undefined)
167+
.catch(ex => traceError('Python Extension: interpreterManager.refresh', ex));
168+
169+
// Activate data science features
170+
const dataScience = serviceManager.get<IDataScience>(IDataScience);
171+
dataScience.activate().ignoreErrors();
172+
173+
context.subscriptions.push(new LinterCommands(serviceManager));
174+
175+
languages.setLanguageConfiguration(PYTHON_LANGUAGE, getLanguageConfiguration());
176+
177+
if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'internalConsole') {
178+
const formatProvider = new PythonFormattingEditProvider(context, serviceContainer);
179+
context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider));
180+
context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider));
181+
}
182+
183+
const deprecationMgr = serviceContainer.get<IFeatureDeprecationManager>(IFeatureDeprecationManager);
184+
deprecationMgr.initialize();
185+
context.subscriptions.push(deprecationMgr);
186+
187+
context.subscriptions.push(new ReplProvider(serviceContainer));
188+
189+
const terminalProvider = new TerminalProvider(serviceContainer);
190+
terminalProvider.initialize(window.activeTerminal).ignoreErrors();
191+
context.subscriptions.push(terminalProvider);
192+
193+
context.subscriptions.push(
194+
languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), {
195+
providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports]
196+
})
197+
);
198+
199+
serviceContainer.getAll<DebugConfigurationProvider>(IDebugConfigurationService).forEach(debugConfigProvider => {
200+
context.subscriptions.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider));
201+
});
202+
203+
serviceContainer.get<IDebuggerBanner>(IDebuggerBanner).initialize();
204+
205+
return activationPromise;
206+
}

src/client/extensionInit.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable:max-func-body-length
7+
8+
import { Container } from 'inversify';
9+
import { Disposable, Memento } from 'vscode';
10+
11+
import { GLOBAL_MEMENTO, IDisposableRegistry, IExtensionContext, IMemento, WORKSPACE_MEMENTO } from './common/types';
12+
import { ServiceContainer } from './ioc/container';
13+
import { ServiceManager } from './ioc/serviceManager';
14+
import { IServiceContainer, IServiceManager } from './ioc/types';
15+
16+
// The code in this module should do nothing more complex than register
17+
// objects to DI and simple init (e.g. no side effects). That implies
18+
// that constructors are likewise simple and do no work. It also means
19+
// that it is inherently synchronous.
20+
21+
export function initializeGlobals(context: IExtensionContext): [IServiceManager, IServiceContainer] {
22+
const cont = new Container();
23+
const serviceManager = new ServiceManager(cont);
24+
const serviceContainer = new ServiceContainer(cont);
25+
26+
serviceManager.addSingletonInstance<IServiceContainer>(IServiceContainer, serviceContainer);
27+
serviceManager.addSingletonInstance<IServiceManager>(IServiceManager, serviceManager);
28+
29+
serviceManager.addSingletonInstance<Disposable[]>(IDisposableRegistry, context.subscriptions);
30+
serviceManager.addSingletonInstance<Memento>(IMemento, context.globalState, GLOBAL_MEMENTO);
31+
serviceManager.addSingletonInstance<Memento>(IMemento, context.workspaceState, WORKSPACE_MEMENTO);
32+
serviceManager.addSingletonInstance<IExtensionContext>(IExtensionContext, context);
33+
34+
return [serviceManager, serviceContainer];
35+
}
36+
37+
export function initializeComponents(
38+
_context: IExtensionContext,
39+
_serviceManager: IServiceManager,
40+
_serviceContainer: IServiceContainer
41+
) {
42+
// We will be pulling code over from activateLegacy().
43+
}

0 commit comments

Comments
 (0)