Skip to content

Remove DI from config launch #20226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 15, 2022
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@
'use strict';

import * as path from 'path';
import { inject, injectable } from 'inversify';
import * as fs from 'fs-extra';
import { injectable } from 'inversify';
import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode';
import { IDynamicDebugConfigurationService } from '../types';
import { IFileSystem } from '../../../common/platform/types';
import { IPathUtils } from '../../../common/types';
import { DebuggerTypeName } from '../../constants';
import { asyncFilter } from '../../../common/utils/arrayUtils';

const workspaceFolderToken = '${workspaceFolder}';

@injectable()
export class DynamicPythonDebugConfigurationService implements IDynamicDebugConfigurationService {
constructor(@inject(IFileSystem) private fs: IFileSystem, @inject(IPathUtils) private pathUtils: IPathUtils) {}

// eslint-disable-next-line class-methods-use-this
public async provideDebugConfigurations(
folder: WorkspaceFolder,
_token?: CancellationToken,
Expand All @@ -32,20 +30,20 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf
justMyCode: true,
});

const djangoManagePath = await this.getDjangoPath(folder);
const djangoManagePath = await DynamicPythonDebugConfigurationService.getDjangoPath(folder);
if (djangoManagePath) {
providers.push({
name: 'Python: Django',
type: DebuggerTypeName,
request: 'launch',
program: `${workspaceFolderToken}${this.pathUtils.separator}${djangoManagePath}`,
program: `${workspaceFolderToken}${path.sep}${djangoManagePath}`,
args: ['runserver'],
django: true,
justMyCode: true,
});
}

const flaskPath = await this.getFlaskPath(folder);
const flaskPath = await DynamicPythonDebugConfigurationService.getFlaskPath(folder);
if (flaskPath) {
providers.push({
name: 'Python: Flask',
Expand All @@ -62,12 +60,9 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf
});
}

let fastApiPath = await this.getFastApiPath(folder);
let fastApiPath = await DynamicPythonDebugConfigurationService.getFastApiPath(folder);
if (fastApiPath) {
fastApiPath = path
.relative(folder.uri.fsPath, fastApiPath)
.replaceAll(this.pathUtils.separator, '.')
.replace('.py', '');
fastApiPath = path.relative(folder.uri.fsPath, fastApiPath).replaceAll(path.sep, '.').replace('.py', '');
providers.push({
name: 'Python: FastAPI',
type: DebuggerTypeName,
Expand All @@ -82,19 +77,19 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf
return providers;
}

private async getDjangoPath(folder: WorkspaceFolder) {
private static async getDjangoPath(folder: WorkspaceFolder) {
const regExpression = /execute_from_command_line\(/;
const possiblePaths = await this.getPossiblePaths(
const possiblePaths = await DynamicPythonDebugConfigurationService.getPossiblePaths(
folder,
['manage.py', '*/manage.py', 'app.py', '*/app.py'],
regExpression,
);
return possiblePaths.length ? path.relative(folder.uri.fsPath, possiblePaths[0]) : null;
}

private async getFastApiPath(folder: WorkspaceFolder) {
private static async getFastApiPath(folder: WorkspaceFolder) {
const regExpression = /app\s*=\s*FastAPI\(/;
const fastApiPaths = await this.getPossiblePaths(
const fastApiPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths(
folder,
['main.py', 'app.py', '*/main.py', '*/app.py', '*/*/main.py', '*/*/app.py'],
regExpression,
Expand All @@ -103,9 +98,9 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf
return fastApiPaths.length ? fastApiPaths[0] : null;
}

private async getFlaskPath(folder: WorkspaceFolder) {
private static async getFlaskPath(folder: WorkspaceFolder) {
const regExpression = /app(?:lication)?\s*=\s*(?:flask\.)?Flask\(|def\s+(?:create|make)_app\(/;
const flaskPaths = await this.getPossiblePaths(
const flaskPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths(
folder,
['__init__.py', 'app.py', 'wsgi.py', '*/__init__.py', '*/app.py', '*/wsgi.py'],
regExpression,
Expand All @@ -114,16 +109,23 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf
return flaskPaths.length ? flaskPaths[0] : null;
}

private async getPossiblePaths(folder: WorkspaceFolder, globPatterns: string[], regex: RegExp): Promise<string[]> {
private static async getPossiblePaths(
folder: WorkspaceFolder,
globPatterns: string[],
regex: RegExp,
): Promise<string[]> {
const foundPathsPromises = (await Promise.allSettled(
globPatterns.map(
async (pattern): Promise<string[]> => this.fs.search(path.join(folder.uri.fsPath, pattern)),
async (pattern): Promise<string[]> =>
(await fs.pathExists(path.join(folder.uri.fsPath, pattern)))
? [path.join(folder.uri.fsPath, pattern)]
: [],
),
)) as { status: string; value: [] }[];
const possiblePaths: string[] = [];
foundPathsPromises.forEach((result) => possiblePaths.push(...result.value));
const finalPaths = await asyncFilter(possiblePaths, async (possiblePath) =>
regex.exec((await this.fs.readFile(possiblePath)).toString()),
regex.exec((await fs.readFile(possiblePath)).toString()),
);

return finalPaths;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,36 @@
// Licensed under the MIT License.

import * as path from 'path';
import * as fs from 'fs-extra';
import { parse } from 'jsonc-parser';
import { inject, injectable } from 'inversify';
import { DebugConfiguration, Uri, WorkspaceFolder } from 'vscode';
import { IFileSystem } from '../../../../common/platform/types';
import { ILaunchJsonReader } from '../types';
import { IWorkspaceService } from '../../../../common/application/types';
import { getWorkspaceFolder } from '../utils/workspaceFolder';

@injectable()
export class LaunchJsonReader implements ILaunchJsonReader {
constructor(
@inject(IFileSystem) private readonly fs: IFileSystem,
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
) {}
export async function getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise<DebugConfiguration[]> {
const filename = path.join(workspace.uri.fsPath, '.vscode', 'launch.json');

public async getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise<DebugConfiguration[]> {
const filename = path.join(workspace.uri.fsPath, '.vscode', 'launch.json');

if (!(await this.fs.fileExists(filename))) {
return [];
}
if (!(await fs.pathExists(filename))) {
return [];
}

const text = await this.fs.readFile(filename);
const parsed = parse(text, [], { allowTrailingComma: true, disallowComments: false });
if (!parsed.configurations || !Array.isArray(parsed.configurations)) {
throw Error('Missing field in launch.json: configurations');
}
if (!parsed.version) {
throw Error('Missing field in launch.json: version');
}
// We do not bother ensuring each item is a DebugConfiguration...
return parsed.configurations;
const text = await fs.readFile(filename, 'utf-8');
const parsed = parse(text, [], { allowTrailingComma: true, disallowComments: false });
if (!parsed.configurations || !Array.isArray(parsed.configurations)) {
throw Error('Missing field in launch.json: configurations');
}
if (!parsed.version) {
throw Error('Missing field in launch.json: version');
}
// We do not bother ensuring each item is a DebugConfiguration...
return parsed.configurations;
}

public async getConfigurationsByUri(uri: Uri): Promise<DebugConfiguration[]> {
const workspace = this.workspaceService.getWorkspaceFolder(uri);
export async function getConfigurationsByUri(uri?: Uri): Promise<DebugConfiguration[]> {
if (uri) {
const workspace = getWorkspaceFolder(uri);
if (workspace) {
return this.getConfigurationsForWorkspace(workspace);
return getConfigurationsForWorkspace(workspace);
}
return [];
}
return [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { inject, injectable } from 'inversify';
import { IExtensionSingleActivationService } from '../../../../activation/types';
import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
import { ICommandManager } from '../../../../common/application/types';
import { IDisposableRegistry } from '../../../../common/types';
import { IDebugConfigurationService } from '../../types';
import { LaunchJsonUpdaterServiceHelper } from './updaterServiceHelper';
Expand All @@ -17,18 +17,11 @@ export class LaunchJsonUpdaterService implements IExtensionSingleActivationServi
constructor(
@inject(ICommandManager) private readonly commandManager: ICommandManager,
@inject(IDisposableRegistry) private readonly disposableRegistry: IDisposableRegistry,
@inject(IWorkspaceService) private readonly workspace: IWorkspaceService,
@inject(IDocumentManager) private readonly documentManager: IDocumentManager,
@inject(IDebugConfigurationService) private readonly configurationProvider: IDebugConfigurationService,
) {}

public async activate(): Promise<void> {
const handler = new LaunchJsonUpdaterServiceHelper(
this.commandManager,
this.workspace,
this.documentManager,
this.configurationProvider,
);
const handler = new LaunchJsonUpdaterServiceHelper(this.commandManager, this.configurationProvider);
this.disposableRegistry.push(
this.commandManager.registerCommand(
'python.SelectAndInsertDebugConfiguration',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@

import { createScanner, parse, SyntaxKind } from 'jsonc-parser';
import { CancellationToken, DebugConfiguration, Position, Range, TextDocument, WorkspaceEdit } from 'vscode';
import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../../../common/application/types';
import { ICommandManager } from '../../../../common/application/types';
import { noop } from '../../../../common/utils/misc';
import { captureTelemetry } from '../../../../telemetry';
import { EventName } from '../../../../telemetry/constants';
import { IDebugConfigurationService } from '../../types';
import { applyEdit, getActiveTextEditor } from '../utils/common';
import { getWorkspaceFolder } from '../utils/workspaceFolder';

type PositionOfCursor = 'InsideEmptyArray' | 'BeforeItem' | 'AfterItem';
type PositionOfComma = 'BeforeCursor';

export class LaunchJsonUpdaterServiceHelper {
constructor(
private readonly commandManager: ICommandManager,
private readonly workspace: IWorkspaceService,
private readonly documentManager: IDocumentManager,
private readonly configurationProvider: IDebugConfigurationService,
) {}

Expand All @@ -28,8 +28,9 @@ export class LaunchJsonUpdaterServiceHelper {
position: Position,
token: CancellationToken,
): Promise<void> {
if (this.documentManager.activeTextEditor && this.documentManager.activeTextEditor.document === document) {
const folder = this.workspace.getWorkspaceFolder(document.uri);
const activeTextEditor = getActiveTextEditor();
if (activeTextEditor && activeTextEditor.document === document) {
const folder = getWorkspaceFolder(document.uri);
const configs = await this.configurationProvider.provideDebugConfigurations!(folder, token);

if (!token.isCancellationRequested && Array.isArray(configs) && configs.length > 0) {
Expand Down Expand Up @@ -66,7 +67,7 @@ export class LaunchJsonUpdaterServiceHelper {
const formattedJson = LaunchJsonUpdaterServiceHelper.getTextForInsertion(config, cursorPosition, commaPosition);
const workspaceEdit = new WorkspaceEdit();
workspaceEdit.insert(document.uri, position, formattedJson);
await this.documentManager.applyEdit(workspaceEdit);
await applyEdit(workspaceEdit);
this.commandManager.executeCommand('editor.action.formatDocument').then(noop, noop);
}

Expand Down
8 changes: 1 addition & 7 deletions src/client/debugger/extension/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

'use strict';

import { CancellationToken, DebugConfiguration, Uri, WorkspaceFolder } from 'vscode';
import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode';

export const IDebugConfigurationResolver = Symbol('IDebugConfigurationResolver');
export interface IDebugConfigurationResolver<T extends DebugConfiguration> {
Expand All @@ -19,9 +19,3 @@ export interface IDebugConfigurationResolver<T extends DebugConfiguration> {
token?: CancellationToken,
): Promise<T | undefined>;
}

export const ILaunchJsonReader = Symbol('ILaunchJsonReader');
export interface ILaunchJsonReader {
getConfigurationsForWorkspace(workspace: WorkspaceFolder): Promise<DebugConfiguration[]>;
getConfigurationsByUri(uri?: Uri): Promise<DebugConfiguration[]>;
}
10 changes: 7 additions & 3 deletions src/client/debugger/extension/configuration/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

'use strict';

import { WorkspaceFolder, window, TextEditor } from 'vscode';
import { WorkspaceFolder, window, TextEditor, WorkspaceEdit, workspace } from 'vscode';
import { getWorkspaceFolder } from './workspaceFolder';

/**
Expand All @@ -26,9 +26,9 @@ export function resolveVariables(
folder: WorkspaceFolder | undefined,
): string | undefined {
if (value) {
const workspace = folder ? getWorkspaceFolder(folder.uri) : undefined;
const workspaceFolder = folder ? getWorkspaceFolder(folder.uri) : undefined;
const variablesObject: { [key: string]: any } = {};
variablesObject.workspaceFolder = workspace ? workspace.uri.fsPath : rootFolder;
variablesObject.workspaceFolder = workspaceFolder ? workspaceFolder.uri.fsPath : rootFolder;

const regexp = /\$\{(.*?)\}/g;
return value.replace(regexp, (match: string, name: string) => {
Expand All @@ -46,3 +46,7 @@ export function getActiveTextEditor(): TextEditor | undefined {
const { activeTextEditor } = window;
return activeTextEditor;
}

export function applyEdit(edit: WorkspaceEdit): Thenable<boolean> {
return workspace.applyEdit(edit);
}
9 changes: 4 additions & 5 deletions src/client/debugger/extension/debugCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { Commands } from '../../common/constants';
import { IDisposableRegistry } from '../../common/types';
import { sendTelemetryEvent } from '../../telemetry';
import { EventName } from '../../telemetry/constants';
import { ILaunchJsonReader } from './configuration/types';
import { DebugPurpose, LaunchRequestArguments } from '../types';
import { IInterpreterService } from '../../interpreter/contracts';
import { noop } from '../../common/utils/misc';
import { getConfigurationsByUri } from './configuration/launch.json/launchJsonReader';

@injectable()
export class DebugCommands implements IExtensionSingleActivationService {
Expand All @@ -22,7 +22,6 @@ export class DebugCommands implements IExtensionSingleActivationService {
constructor(
@inject(ICommandManager) private readonly commandManager: ICommandManager,
@inject(IDebugService) private readonly debugService: IDebugService,
@inject(ILaunchJsonReader) private readonly launchJsonReader: ILaunchJsonReader,
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
) {}
Expand All @@ -36,15 +35,15 @@ export class DebugCommands implements IExtensionSingleActivationService {
this.commandManager.executeCommand(Commands.TriggerEnvironmentSelection, file).then(noop, noop);
return;
}
const config = await this.getDebugConfiguration(file);
const config = await DebugCommands.getDebugConfiguration(file);
this.debugService.startDebugging(undefined, config);
}),
);
return Promise.resolve();
}

private async getDebugConfiguration(uri?: Uri): Promise<DebugConfiguration> {
const configs = (await this.launchJsonReader.getConfigurationsByUri(uri)).filter((c) => c.request === 'launch');
private static async getDebugConfiguration(uri?: Uri): Promise<DebugConfiguration> {
const configs = (await getConfigurationsByUri(uri)).filter((c) => c.request === 'launch');
for (const config of configs) {
if ((config as LaunchRequestArguments).purpose?.includes(DebugPurpose.DebugInTerminal)) {
if (!config.program && !config.module && !config.code) {
Expand Down
Loading