Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions Source/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
out
server
node_modules
9 changes: 9 additions & 0 deletions Source/client/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.vscode/**
typings/**
out/test/**
test/**
src/**
**/*.map
.gitignore
tsconfig.json
vsc-extension-quickstart.md
211 changes: 211 additions & 0 deletions Source/client/activation/activationManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

"use strict";

import { inject, injectable, multiInject } from "inversify";
import { TextDocument } from "vscode";
import { IApplicationDiagnostics } from "../application/types";
import {
IActiveResourceService,
IDocumentManager,
IWorkspaceService,
} from "../common/application/types";
import { PYTHON_LANGUAGE } from "../common/constants";
import { IFileSystem } from "../common/platform/types";
import {
IDisposable,
IInterpreterPathService,
Resource,
} from "../common/types";
import { Deferred } from "../common/utils/async";
import { IInterpreterAutoSelectionService } from "../interpreter/autoSelection/types";
import { traceDecoratorError } from "../logging";
import { sendActivationTelemetry } from "../telemetry/envFileTelemetry";
import {
IExtensionActivationManager,
IExtensionActivationService,
IExtensionSingleActivationService,
} from "./types";

@injectable()
export class ExtensionActivationManager implements IExtensionActivationManager {
public readonly activatedWorkspaces = new Set<string>();

protected readonly isInterpreterSetForWorkspacePromises = new Map<
string,
Deferred<void>
>();

private readonly disposables: IDisposable[] = [];

private docOpenedHandler?: IDisposable;

constructor(
@multiInject(IExtensionActivationService)
private activationServices: IExtensionActivationService[],
@multiInject(IExtensionSingleActivationService)
private singleActivationServices: IExtensionSingleActivationService[],
@inject(IDocumentManager)
private readonly documentManager: IDocumentManager,
@inject(IInterpreterAutoSelectionService)
private readonly autoSelection: IInterpreterAutoSelectionService,
@inject(IApplicationDiagnostics)
private readonly appDiagnostics: IApplicationDiagnostics,
@inject(IWorkspaceService)
private readonly workspaceService: IWorkspaceService,
@inject(IFileSystem) private readonly fileSystem: IFileSystem,
@inject(IActiveResourceService)
private readonly activeResourceService: IActiveResourceService,
@inject(IInterpreterPathService)
private readonly interpreterPathService: IInterpreterPathService
) {}

private filterServices() {
if (!this.workspaceService.isTrusted) {
this.activationServices = this.activationServices.filter(
(service) => service.supportedWorkspaceTypes.untrustedWorkspace
);
this.singleActivationServices =
this.singleActivationServices.filter(
(service) =>
service.supportedWorkspaceTypes.untrustedWorkspace
);
}
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) {
const disposable = this.disposables.shift()!;
disposable.dispose();
}
if (this.docOpenedHandler) {
this.docOpenedHandler.dispose();
this.docOpenedHandler = undefined;
}
}

public async activate(): Promise<void> {
this.filterServices();
await this.initialize();

// Activate all activation services together.

await Promise.all([
...this.singleActivationServices.map((item) => item.activate()),
this.activateWorkspace(
this.activeResourceService.getActiveResource()
),
]);
}

@traceDecoratorError("Failed to activate a workspace")
public async activateWorkspace(resource: Resource): Promise<void> {
const key = this.getWorkspaceKey(resource);
if (this.activatedWorkspaces.has(key)) {
return;
}
this.activatedWorkspaces.add(key);

if (this.workspaceService.isTrusted) {
// Do not interact with interpreters in a untrusted workspace.
await this.autoSelection.autoSelectInterpreter(resource);
await this.interpreterPathService.copyOldInterpreterStorageValuesToNew(
resource
);
}
await sendActivationTelemetry(
this.fileSystem,
this.workspaceService,
resource
);
await Promise.all(
this.activationServices.map((item) => item.activate(resource))
);
await this.appDiagnostics.performPreStartupHealthCheck(resource);
}

public async initialize(): Promise<void> {
this.addHandlers();
this.addRemoveDocOpenedHandlers();
}

public onDocOpened(doc: TextDocument): void {
if (doc.languageId !== PYTHON_LANGUAGE) {
return;
}
const key = this.getWorkspaceKey(doc.uri);
const hasWorkspaceFolders =
(this.workspaceService.workspaceFolders?.length || 0) > 0;
// If we have opened a doc that does not belong to workspace, then do nothing.
if (key === "" && hasWorkspaceFolders) {
return;
}
if (this.activatedWorkspaces.has(key)) {
return;
}
const folder = this.workspaceService.getWorkspaceFolder(doc.uri);
this.activateWorkspace(folder ? folder.uri : undefined).ignoreErrors();
}

protected addHandlers(): void {
this.disposables.push(
this.workspaceService.onDidChangeWorkspaceFolders(
this.onWorkspaceFoldersChanged,
this
)
);
}

protected addRemoveDocOpenedHandlers(): void {
if (this.hasMultipleWorkspaces()) {
if (!this.docOpenedHandler) {
this.docOpenedHandler =
this.documentManager.onDidOpenTextDocument(
this.onDocOpened,
this
);
}
return;
}
if (this.docOpenedHandler) {
this.docOpenedHandler.dispose();
this.docOpenedHandler = undefined;
}
}

protected onWorkspaceFoldersChanged(): void {
// If an activated workspace folder was removed, delete its key
const workspaceKeys = this.workspaceService.workspaceFolders!.map(
(workspaceFolder) => this.getWorkspaceKey(workspaceFolder.uri)
);
const activatedWkspcKeys = Array.from(this.activatedWorkspaces.keys());
const activatedWkspcFoldersRemoved = activatedWkspcKeys.filter(
(item) => workspaceKeys.indexOf(item) < 0
);
if (activatedWkspcFoldersRemoved.length > 0) {
for (const folder of activatedWkspcFoldersRemoved) {
this.activatedWorkspaces.delete(folder);
}
}
this.addRemoveDocOpenedHandlers();
}

protected hasMultipleWorkspaces(): boolean {
return (this.workspaceService.workspaceFolders?.length || 0) > 1;
}

protected getWorkspaceKey(resource: Resource): string {
return this.workspaceService.getWorkspaceFolderIdentifier(resource, "");
}
}
7 changes: 7 additions & 0 deletions Source/client/activation/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
"use strict";

export namespace Commands {
export const RestartLS = "python.analysis.restartLanguageServer";
}
133 changes: 133 additions & 0 deletions Source/client/activation/common/analysisOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// 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 { ILogOutputChannel, Resource } from "../../common/types";
import { debounceSync } from "../../common/utils/decorators";
import { IEnvironmentVariablesProvider } from "../../common/variables/types";
import { traceDecoratorError } from "../../logging";
import { PythonEnvironment } from "../../pythonEnvironments/info";
import {
ILanguageServerAnalysisOptions,
ILanguageServerOutputChannel,
} from "../types";

export abstract class LanguageServerAnalysisOptionsBase
implements ILanguageServerAnalysisOptions
{
protected readonly didChange = new EventEmitter<void>();
private readonly output: ILogOutputChannel;

protected constructor(
lsOutputChannel: ILanguageServerOutputChannel,
protected readonly workspace: IWorkspaceService
) {
this.output = lsOutputChannel.channel;
}

public async initialize(
_resource: Resource,
_interpreter: PythonEnvironment | undefined
) {}

public get onDidChange(): Event<void> {
return this.didChange.event;
}

public dispose(): void {
this.didChange.dispose();
}

@traceDecoratorError("Failed to get analysis options")
public async getAnalysisOptions(): Promise<LanguageClientOptions> {
const workspaceFolder = this.getWorkspaceFolder();
const documentSelector = this.getDocumentFilters(workspaceFolder);

return {
documentSelector,
workspaceFolder,
synchronize: {
configurationSection: this.getConfigSectionsToSynchronize(),
},
outputChannel: this.output,
revealOutputChannelOn: RevealOutputChannelOn.Never,
initializationOptions: await this.getInitializationOptions(),
};
}

protected getWorkspaceFolder(): WorkspaceFolder | undefined {
return undefined;
}

protected getDocumentFilters(
_workspaceFolder?: WorkspaceFolder
): DocumentFilter[] {
return this.workspace.isVirtualWorkspace
? [{ language: PYTHON_LANGUAGE }]
: PYTHON;
}

protected getConfigSectionsToSynchronize(): string[] {
return [PYTHON_LANGUAGE];
}

protected async getInitializationOptions(): Promise<any> {
return undefined;
}
}

export abstract class LanguageServerAnalysisOptionsWithEnv extends LanguageServerAnalysisOptionsBase {
protected disposables: Disposable[] = [];
private envPythonPath: string = "";

protected constructor(
private readonly envVarsProvider: IEnvironmentVariablesProvider,
lsOutputChannel: ILanguageServerOutputChannel,
workspace: IWorkspaceService
) {
super(lsOutputChannel, workspace);
}

public async initialize(
_resource: Resource,
_interpreter: PythonEnvironment | undefined
) {
const disposable = this.envVarsProvider.onDidEnvironmentVariablesChange(
this.onEnvVarChange,
this
);
this.disposables.push(disposable);
}

public dispose(): void {
super.dispose();
this.disposables.forEach((d) => d.dispose());
}

protected async getEnvPythonPath(): Promise<string> {
const vars = await this.envVarsProvider.getEnvironmentVariables();
this.envPythonPath = vars.PYTHONPATH || "";
return this.envPythonPath;
}

@debounceSync(1000)
protected onEnvVarChange(): void {
this.notifyifEnvPythonPathChanged().ignoreErrors();
}

protected async notifyifEnvPythonPathChanged(): Promise<void> {
const vars = await this.envVarsProvider.getEnvironmentVariables();
const envPythonPath = vars.PYTHONPATH || "";

if (this.envPythonPath !== envPythonPath) {
this.didChange.fire();
}
}
}
Loading