From 2dbd6688cba9db99b63d0d09f46eada68947fc79 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Thu, 15 Feb 2024 14:39:16 -0800 Subject: [PATCH 01/12] move config common functions to utils --- .../dynamicdebugConfigurationService.ts | 66 ++---------------- .../configuration/utils/configuration.ts | 67 +++++++++++++++++++ 2 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts index 3296f4c7..8b81dc69 100644 --- a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts +++ b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts @@ -5,12 +5,11 @@ 'use strict'; import * as path from 'path'; -import * as fs from 'fs-extra'; import { CancellationToken, DebugConfiguration, WorkspaceFolder } from 'vscode'; import { IDynamicDebugConfigurationService } from '../types'; -import { asyncFilter } from '../../common/utilities'; import { DebuggerTypeName } from '../../constants'; import { replaceAll } from '../../common/stringUtils'; +import { getDjangoPaths, getFastApiPaths, getFlaskPaths } from './utils/configuration'; const workspaceFolderToken = '${workspaceFolder}'; @@ -29,7 +28,8 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf program: '${file}', }); - const djangoManagePath = await DynamicPythonDebugConfigurationService.getDjangoPath(folder); + const djangoManagePaths = await getDjangoPaths(folder); + const djangoManagePath = djangoManagePaths?.length ? path.relative(folder.uri.fsPath, djangoManagePaths[0]) : null; if (djangoManagePath) { providers.push({ name: 'Python Debugger: Django', @@ -41,7 +41,8 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf }); } - const flaskPath = await DynamicPythonDebugConfigurationService.getFlaskPath(folder); + const flaskPaths = await getFlaskPaths(folder); + const flaskPath = flaskPaths?.length ? flaskPaths[0] : null; if (flaskPath) { providers.push({ name: 'Python Debugger: Flask', @@ -57,7 +58,8 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf }); } - let fastApiPath = await DynamicPythonDebugConfigurationService.getFastApiPath(folder); + const fastApiPaths = await getFastApiPaths(folder); + let fastApiPath = fastApiPaths?.length ? fastApiPaths[0] : null; if (fastApiPath) { fastApiPath = replaceAll(path.relative(folder.uri.fsPath, fastApiPath), path.sep, '.').replace('.py', ''); providers.push({ @@ -72,58 +74,4 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf return providers; } - - private static async getDjangoPath(folder: WorkspaceFolder) { - const regExpression = /execute_from_command_line\(/; - 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 static async getFastApiPath(folder: WorkspaceFolder) { - const regExpression = /app\s*=\s*FastAPI\(/; - const fastApiPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths( - folder, - ['main.py', 'app.py', '*/main.py', '*/app.py', '*/*/main.py', '*/*/app.py'], - regExpression, - ); - - return fastApiPaths.length ? fastApiPaths[0] : null; - } - - private static async getFlaskPath(folder: WorkspaceFolder) { - const regExpression = /app(?:lication)?\s*=\s*(?:flask\.)?Flask\(|def\s+(?:create|make)_app\(/; - const flaskPaths = await DynamicPythonDebugConfigurationService.getPossiblePaths( - folder, - ['__init__.py', 'app.py', 'wsgi.py', '*/__init__.py', '*/app.py', '*/wsgi.py'], - regExpression, - ); - - return flaskPaths.length ? flaskPaths[0] : null; - } - - private static async getPossiblePaths( - folder: WorkspaceFolder, - globPatterns: string[], - regex: RegExp, - ): Promise { - const foundPathsPromises = (await Promise.allSettled( - globPatterns.map( - async (pattern): Promise => - (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 fs.readFile(possiblePath)).toString()), - ); - - return finalPaths; - } } diff --git a/src/extension/debugger/configuration/utils/configuration.ts b/src/extension/debugger/configuration/utils/configuration.ts index f880d1a9..7c62ab8c 100644 --- a/src/extension/debugger/configuration/utils/configuration.ts +++ b/src/extension/debugger/configuration/utils/configuration.ts @@ -6,12 +6,16 @@ 'use strict'; +import * as path from 'path'; +import * as fs from 'fs-extra'; import { MultiStepInput } from '../../../common/multiStepInput'; import { sendTelemetryEvent } from '../../../telemetry'; import { EventName } from '../../../telemetry/constants'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { AttachRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; +import { WorkspaceFolder } from 'vscode'; +import { asyncFilter } from '../../../common/utilities'; const defaultPort = 5678; @@ -42,3 +46,66 @@ export async function configurePort( manuallyEnteredAValue: connect.port !== defaultPort, }); } + +async function getPossiblePaths( + folder: WorkspaceFolder, + globPatterns: string[], + regex: RegExp, +): Promise { + const foundPathsPromises = (await Promise.allSettled( + globPatterns.map( + async (pattern): Promise => + (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 fs.readFile(possiblePath)).toString()), + ); + + return finalPaths; +} + +export async function getDjangoPaths(folder: WorkspaceFolder | undefined) { + if (!folder) { + return undefined; + } + const regExpression = /execute_from_command_line\(/; + const possiblePaths = await getPossiblePaths( + folder, + ['manage.py', '*/manage.py', 'app.py', '*/app.py'], + regExpression, + ); + return possiblePaths; +} + +export async function getFastApiPaths(folder: WorkspaceFolder | undefined) { + if (!folder) { + return undefined; + } + const regExpression = /app\s*=\s*FastAPI\(/; + const fastApiPaths = await getPossiblePaths( + folder, + ['main.py', 'app.py', '*/main.py', '*/app.py', '*/*/main.py', '*/*/app.py'], + regExpression, + ); + + return fastApiPaths.length ? fastApiPaths[0] : null; +} + +export async function getFlaskPaths(folder: WorkspaceFolder | undefined) { + if (!folder) { + return undefined; + } + const regExpression = /app(?:lication)?\s*=\s*(?:flask\.)?Flask\(|def\s+(?:create|make)_app\(/; + const flaskPaths = await getPossiblePaths( + folder, + ['__init__.py', 'app.py', 'wsgi.py', '*/__init__.py', '*/app.py', '*/wsgi.py'], + regExpression, + ); + + return flaskPaths.length ? flaskPaths[0] : null; +} \ No newline at end of file From 781be94831b41d4706c397ed20dd4f3bcd681a49 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Thu, 15 Feb 2024 14:39:31 -0800 Subject: [PATCH 02/12] get django paths --- .../debugger/configuration/providers/djangoLaunch.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/extension/debugger/configuration/providers/djangoLaunch.ts b/src/extension/debugger/configuration/providers/djangoLaunch.ts index 3aa75e33..d19fbe41 100644 --- a/src/extension/debugger/configuration/providers/djangoLaunch.ts +++ b/src/extension/debugger/configuration/providers/djangoLaunch.ts @@ -14,6 +14,7 @@ import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; import { resolveVariables } from '../utils/common'; import { DebugConfigStrings } from '../../../common/utils/localize'; +import { getDjangoPaths } from '../utils/configuration'; const workspaceFolderToken = '${workspaceFolder}'; @@ -21,14 +22,15 @@ export async function buildDjangoLaunchDebugConfiguration( input: MultiStepInput, state: DebugConfigurationState, ): Promise { - const program = await getManagePyPath(state.folder); + // const program = await getManagePyPath(state.folder); + let djangoPaths = await getDjangoPaths(state.folder) + let manuallyEnteredAValue: boolean | undefined; - const defaultProgram = `${workspaceFolderToken}${path.sep}manage.py`; + const config: Partial = { name: DebugConfigStrings.django.snippet.name, type: DebuggerTypeName, request: 'launch', - program: program || defaultProgram, args: ['runserver'], django: true, autoStartBrowser: false, From bf218f29c5d438a3624ee6dd328ed6d9a89223cf Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 13 Mar 2024 15:18:47 -0700 Subject: [PATCH 03/12] fix error in getting paths --- .../dynamicdebugConfigurationService.ts | 6 +-- .../configuration/utils/configuration.ts | 50 ++++++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts index 8b81dc69..14ee49c5 100644 --- a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts +++ b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts @@ -29,7 +29,7 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf }); const djangoManagePaths = await getDjangoPaths(folder); - const djangoManagePath = djangoManagePaths?.length ? path.relative(folder.uri.fsPath, djangoManagePaths[0]) : null; + const djangoManagePath = djangoManagePaths?.length ? path.relative(folder.uri.fsPath, djangoManagePaths[0].fsPath) : null; if (djangoManagePath) { providers.push({ name: 'Python Debugger: Django', @@ -42,7 +42,7 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf } const flaskPaths = await getFlaskPaths(folder); - const flaskPath = flaskPaths?.length ? flaskPaths[0] : null; + const flaskPath = flaskPaths?.length ? flaskPaths[0].fsPath : null; if (flaskPath) { providers.push({ name: 'Python Debugger: Flask', @@ -59,7 +59,7 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf } const fastApiPaths = await getFastApiPaths(folder); - let fastApiPath = fastApiPaths?.length ? fastApiPaths[0] : null; + let fastApiPath = fastApiPaths?.length ? fastApiPaths[0].fsPath : null; if (fastApiPath) { fastApiPath = replaceAll(path.relative(folder.uri.fsPath, fastApiPath), path.sep, '.').replace('.py', ''); providers.push({ diff --git a/src/extension/debugger/configuration/utils/configuration.ts b/src/extension/debugger/configuration/utils/configuration.ts index 7c62ab8c..cf2c9466 100644 --- a/src/extension/debugger/configuration/utils/configuration.ts +++ b/src/extension/debugger/configuration/utils/configuration.ts @@ -14,7 +14,7 @@ import { EventName } from '../../../telemetry/constants'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { AttachRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -import { WorkspaceFolder } from 'vscode'; +import { Uri, WorkspaceFolder, workspace } from 'vscode'; import { asyncFilter } from '../../../common/utilities'; const defaultPort = 5678; @@ -47,23 +47,42 @@ export async function configurePort( }); } +// async function getPossiblePaths( +// folder: WorkspaceFolder, +// globPatterns: string[], +// regex: RegExp, +// ): Promise { +// const foundPathsPromises = (await Promise.allSettled( +// globPatterns.map( +// async (pattern): Promise => +// (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 fs.readFile(possiblePath)).toString()), +// ); + +// return finalPaths; +// } + async function getPossiblePaths( - folder: WorkspaceFolder, globPatterns: string[], regex: RegExp, -): Promise { +): Promise { const foundPathsPromises = (await Promise.allSettled( globPatterns.map( - async (pattern): Promise => - (await fs.pathExists(path.join(folder.uri.fsPath, pattern))) - ? [path.join(folder.uri.fsPath, pattern)] - : [], + async (pattern): Promise => + (await workspace.findFiles(pattern)) ), )) as { status: string; value: [] }[]; - const possiblePaths: string[] = []; + const possiblePaths: Uri[] = []; foundPathsPromises.forEach((result) => possiblePaths.push(...result.value)); const finalPaths = await asyncFilter(possiblePaths, async (possiblePath) => - regex.exec((await fs.readFile(possiblePath)).toString()), + regex.exec((await fs.readFile(possiblePath.fsPath)).toString()), ); return finalPaths; @@ -74,12 +93,11 @@ export async function getDjangoPaths(folder: WorkspaceFolder | undefined) { return undefined; } const regExpression = /execute_from_command_line\(/; - const possiblePaths = await getPossiblePaths( - folder, + const djangoPaths = await getPossiblePaths( ['manage.py', '*/manage.py', 'app.py', '*/app.py'], regExpression, ); - return possiblePaths; + return djangoPaths; } export async function getFastApiPaths(folder: WorkspaceFolder | undefined) { @@ -88,12 +106,11 @@ export async function getFastApiPaths(folder: WorkspaceFolder | undefined) { } const regExpression = /app\s*=\s*FastAPI\(/; const fastApiPaths = await getPossiblePaths( - folder, ['main.py', 'app.py', '*/main.py', '*/app.py', '*/*/main.py', '*/*/app.py'], regExpression, ); - return fastApiPaths.length ? fastApiPaths[0] : null; + return fastApiPaths; } export async function getFlaskPaths(folder: WorkspaceFolder | undefined) { @@ -102,10 +119,9 @@ export async function getFlaskPaths(folder: WorkspaceFolder | undefined) { } const regExpression = /app(?:lication)?\s*=\s*(?:flask\.)?Flask\(|def\s+(?:create|make)_app\(/; const flaskPaths = await getPossiblePaths( - folder, ['__init__.py', 'app.py', 'wsgi.py', '*/__init__.py', '*/app.py', '*/wsgi.py'], regExpression, ); - return flaskPaths.length ? flaskPaths[0] : null; -} \ No newline at end of file + return flaskPaths; +} From 6f46f6bc7046ca6a86b49e6ad5abac2c3b36a276 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Mon, 18 Mar 2024 20:00:54 -0700 Subject: [PATCH 04/12] Add prompt to show django paths --- src/extension/common/multiStepInput.ts | 101 +++++++------ src/extension/common/utils/localize.ts | 13 +- .../debugConfigurationService.ts | 5 +- .../dynamicdebugConfigurationService.ts | 4 +- .../configuration/providers/djangoLaunch.ts | 134 +++++++++++------- .../providers/pathQuickPick/providerPicker.ts | 29 ++++ .../providers/pathQuickPick/types.ts | 16 +++ .../configuration/utils/configuration.ts | 42 +----- src/extension/extensionInit.ts | 4 +- src/extension/telemetry/index.ts | 10 +- 10 files changed, 217 insertions(+), 141 deletions(-) create mode 100644 src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts create mode 100644 src/extension/debugger/configuration/providers/pathQuickPick/types.ts diff --git a/src/extension/common/multiStepInput.ts b/src/extension/common/multiStepInput.ts index 6f28506c..fd848c16 100644 --- a/src/extension/common/multiStepInput.ts +++ b/src/extension/common/multiStepInput.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -15,7 +14,9 @@ import { QuickPickItem, Event, window, + QuickPickItemButtonEvent, } from 'vscode'; +import { createDeferred } from './utils/async'; // Borrowed from https://github.com/Microsoft/vscode-extension-samples/blob/master/quickinput-sample/src/multiStepInput.ts // Why re-invent the wheel :) @@ -37,7 +38,7 @@ export type InputStep = (input: MultiStepInput, state: T) => P type buttonCallbackType = (quickPick: QuickPick) => void; -type QuickInputButtonSetup = { +export type QuickInputButtonSetup = { /** * Button for an action in a QuickPick. */ @@ -54,13 +55,12 @@ export interface IQuickPickParameters { totalSteps?: number; canGoBack?: boolean; items: T[]; - activeItem?: T | Promise; + activeItem?: T | ((quickPick: QuickPick) => Promise); placeholder: string | undefined; customButtonSetups?: QuickInputButtonSetup[]; matchOnDescription?: boolean; matchOnDetail?: boolean; keepScrollPosition?: boolean; - sortByLabel?: boolean; acceptFilterBoxTextAsSelection?: boolean; /** * A method called only after quickpick has been created and all handlers are registered. @@ -70,6 +70,7 @@ export interface IQuickPickParameters { callback: (event: E, quickPick: QuickPick) => void; event: Event; }; + onDidTriggerItemButton?: (e: QuickPickItemButtonEvent) => void; } interface InputBoxParameters { @@ -83,7 +84,7 @@ interface InputBoxParameters { validate(value: string): Promise; } -type MultiStepInputQuickPicResponseType = T | (P extends { buttons: (infer I)[] } ? I : never) | undefined; +type MultiStepInputQuickPickResponseType = T | (P extends { buttons: (infer I)[] } ? I : never) | undefined; type MultiStepInputInputBoxResponseType

= string | (P extends { buttons: (infer I)[] } ? I : never) | undefined; export interface IMultiStepInput { run(start: InputStep, state: S): Promise; @@ -95,7 +96,7 @@ export interface IMultiStepInput { activeItem, placeholder, customButtonSetups, - }: P): Promise>; + }: P): Promise>; showInputBox

({ title, step, @@ -131,8 +132,9 @@ export class MultiStepInput implements IMultiStepInput { acceptFilterBoxTextAsSelection, onChangeItem, keepScrollPosition, + onDidTriggerItemButton, initialize, - }: P): Promise> { + }: P): Promise> { const disposables: Disposable[] = []; const input = window.createQuickPick(); input.title = title; @@ -161,7 +163,13 @@ export class MultiStepInput implements IMultiStepInput { initialize(input); } if (activeItem) { - input.activeItems = [await activeItem]; + if (typeof activeItem === 'function') { + activeItem(input).then((item) => { + if (input.activeItems.length === 0) { + input.activeItems = [item]; + } + }); + } } else { input.activeItems = []; } @@ -170,35 +178,46 @@ export class MultiStepInput implements IMultiStepInput { // so do it after initialization. This ensures quickpick starts with the active // item in focus when this is true, instead of having scroll position at top. input.keepScrollPosition = keepScrollPosition; - try { - return await new Promise>((resolve, reject) => { - disposables.push( - input.onDidTriggerButton(async (item) => { - if (item === QuickInputButtons.Back) { - reject(InputFlowAction.back); - } - if (customButtonSetups) { - for (const customButtonSetup of customButtonSetups) { - if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) { - await customButtonSetup?.callback(input); - } - } + + const deferred = createDeferred(); + + disposables.push( + input.onDidTriggerButton(async (item) => { + if (item === QuickInputButtons.Back) { + deferred.reject(InputFlowAction.back); + input.hide(); + } + if (customButtonSetups) { + for (const customButtonSetup of customButtonSetups) { + if (JSON.stringify(item) === JSON.stringify(customButtonSetup?.button)) { + await customButtonSetup?.callback(input); } - }), - input.onDidChangeSelection((selectedItems) => resolve(selectedItems[0])), - input.onDidHide(() => { - resolve(undefined); - }), - ); - if (acceptFilterBoxTextAsSelection) { - disposables.push( - input.onDidAccept(() => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - resolve(input.value as any); - }), - ); + } } - }); + }), + input.onDidChangeSelection((selectedItems) => deferred.resolve(selectedItems[0])), + input.onDidHide(() => { + if (!deferred.completed) { + deferred.resolve(undefined); + } + }), + input.onDidTriggerItemButton(async (item) => { + if (onDidTriggerItemButton) { + await onDidTriggerItemButton(item); + } + }), + ); + if (acceptFilterBoxTextAsSelection) { + disposables.push( + input.onDidAccept(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + deferred.resolve(input.value as any); + }), + ); + } + + try { + return await deferred.promise; } finally { disposables.forEach((d) => d.dispose()); } @@ -283,6 +302,9 @@ export class MultiStepInput implements IMultiStepInput { if (err === InputFlowAction.back) { this.steps.pop(); step = this.steps.pop(); + if (step === undefined) { + throw err; + } } else if (err === InputFlowAction.resume) { step = this.steps.pop(); } else if (err === InputFlowAction.cancel) { @@ -297,12 +319,3 @@ export class MultiStepInput implements IMultiStepInput { } } } -export const IMultiStepInputFactory = Symbol('IMultiStepInputFactory'); -export interface IMultiStepInputFactory { - create(): IMultiStepInput; -} -export class MultiStepInputFactory { - public create(): IMultiStepInput { - return new MultiStepInput(); - } -} diff --git a/src/extension/common/utils/localize.ts b/src/extension/common/utils/localize.ts index 06b6fbdb..0b2266eb 100644 --- a/src/extension/common/utils/localize.ts +++ b/src/extension/common/utils/localize.ts @@ -95,7 +95,7 @@ export namespace DebugConfigStrings { export const enterManagePyPath = { title: l10n.t('Debug Django'), prompt: l10n.t( - "Enter the path to manage.py ('${workspaceFolderToken}' points to the root of the current workspace folder)", + "Enter the path to manage.py or select one from the list ('${workspaceFolderToken}' points to the root of the current workspace folder)", ), invalid: l10n.t('Enter a valid Python file path'), }; @@ -170,3 +170,14 @@ export namespace pickArgsInput { export const title = l10n.t('Command Line Arguments'); export const prompt = l10n.t('Enter the command line arguments you want to pass to the program'); } + +// export const providerQuickPick = { +// refreshInterpreterList +// } + +export const browsePath = { + label: l10n.t('Find...'), + detail: l10n.t('Browse your file system to find a Python file.'), + openButtonLabel: l10n.t('Select File'), + title: l10n.t('Select Python File'), +}; diff --git a/src/extension/debugger/configuration/debugConfigurationService.ts b/src/extension/debugger/configuration/debugConfigurationService.ts index 5528d508..13edb6f6 100644 --- a/src/extension/debugger/configuration/debugConfigurationService.ts +++ b/src/extension/debugger/configuration/debugConfigurationService.ts @@ -7,7 +7,7 @@ import { inject, injectable, named } from 'inversify'; import { cloneDeep } from 'lodash'; import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../common/utils/localize'; -import { IMultiStepInputFactory, InputStep, IQuickPickParameters, MultiStepInput } from '../../common/multiStepInput'; +import { InputStep, IQuickPickParameters, MultiStepInput } from '../../common/multiStepInput'; import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; import { buildDjangoLaunchDebugConfiguration } from './providers/djangoLaunch'; @@ -32,7 +32,6 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationResolver) @named('launch') private readonly launchResolver: IDebugConfigurationResolver, - @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, ) {} public async provideDebugConfigurations( @@ -43,7 +42,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi const state = { config, folder, token }; // Disabled until configuration issues are addressed by VS Code. See #4007 - const multiStep = this.multiStepFactory.create(); + const multiStep = new MultiStepInput(); await multiStep.run((input, s) => PythonDebugConfigurationService.pickDebugConfiguration(input, s), state); if (Object.keys(state.config).length !== 0) { diff --git a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts index 14ee49c5..44f8f2d9 100644 --- a/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts +++ b/src/extension/debugger/configuration/dynamicdebugConfigurationService.ts @@ -29,7 +29,9 @@ export class DynamicPythonDebugConfigurationService implements IDynamicDebugConf }); const djangoManagePaths = await getDjangoPaths(folder); - const djangoManagePath = djangoManagePaths?.length ? path.relative(folder.uri.fsPath, djangoManagePaths[0].fsPath) : null; + const djangoManagePath = djangoManagePaths?.length + ? path.relative(folder.uri.fsPath, djangoManagePaths[0].fsPath) + : null; if (djangoManagePath) { providers.push({ name: 'Python Debugger: Django', diff --git a/src/extension/debugger/configuration/providers/djangoLaunch.ts b/src/extension/debugger/configuration/providers/djangoLaunch.ts index d19fbe41..a2f00d37 100644 --- a/src/extension/debugger/configuration/providers/djangoLaunch.ts +++ b/src/extension/debugger/configuration/providers/djangoLaunch.ts @@ -3,18 +3,18 @@ 'use strict'; -import * as vscode from 'vscode'; +import { QuickPick, QuickPickItemButtonEvent, QuickPickItemKind, Uri, WorkspaceFolder, window } from 'vscode'; import * as path from 'path'; -import * as fs from 'fs-extra'; -import { MultiStepInput } from '../../../common/multiStepInput'; +import { IQuickPickParameters, InputFlowAction, MultiStepInput } from '../../../common/multiStepInput'; import { sendTelemetryEvent } from '../../../telemetry'; import { EventName } from '../../../telemetry/constants'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; import { DebugConfigurationState, DebugConfigurationType } from '../../types'; -import { resolveVariables } from '../utils/common'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { getDjangoPaths } from '../utils/configuration'; +import { browseFileOption, goToFileButton, openFileExplorer } from './pathQuickPick/providerPicker'; +import { QuickPickType } from './pathQuickPick/types'; const workspaceFolderToken = '${workspaceFolder}'; @@ -22,11 +22,6 @@ export async function buildDjangoLaunchDebugConfiguration( input: MultiStepInput, state: DebugConfigurationState, ): Promise { - // const program = await getManagePyPath(state.folder); - let djangoPaths = await getDjangoPaths(state.folder) - - let manuallyEnteredAValue: boolean | undefined; - const config: Partial = { name: DebugConfigStrings.django.snippet.name, type: DebuggerTypeName, @@ -35,58 +30,95 @@ export async function buildDjangoLaunchDebugConfiguration( django: true, autoStartBrowser: false, }; - if (!program) { - const selectedProgram = await input.showInputBox({ - title: DebugConfigStrings.django.enterManagePyPath.title, - value: defaultProgram, - prompt: DebugConfigStrings.django.enterManagePyPath.prompt, - validate: (value) => validateManagePy(state.folder, defaultProgram, value), + + let djangoPaths = await getDjangoPaths(state.folder); + + let options: QuickPickType[] = []; + + //add found paths to options + if (djangoPaths.length > 0) { + options.push( + ...djangoPaths.map((item) => ({ + label: path.basename(item.fsPath), + filePath: item, + description: parseManagePyPath(state.folder, item.fsPath), + buttons: [goToFileButton], + })), + ); + } else { + const managePath = path.join(state?.folder?.uri.fsPath || '', 'manage.py'); + options.push({ + label: 'manage.py', + description: parseManagePyPath(state.folder, managePath), + filePath: Uri.file(managePath), }); - if (selectedProgram) { - manuallyEnteredAValue = true; - config.program = selectedProgram; - } else { - return; - } } + await input.run((input, s) => pickDjangoPrompt(input, s, config, options), state); +} - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - autoDetectedDjangoManagePyPath: !!program, - manuallyEnteredAValue, - }); +export async function pickDjangoPrompt( + input: MultiStepInput, + state: DebugConfigurationState, + config: Partial, + pathsOptions: QuickPickType[], +) { + let options: QuickPickType[] = [ + ...pathsOptions, + { label: '', kind: QuickPickItemKind.Separator }, + browseFileOption, + ]; - Object.assign(state.config, config); -} + const selection = await input.showQuickPick>({ + placeholder: DebugConfigStrings.django.enterManagePyPath.prompt, + items: options, + acceptFilterBoxTextAsSelection: true, + activeItem: options[0], + matchOnDescription: true, + title: DebugConfigStrings.django.enterManagePyPath.title, + onDidTriggerItemButton: async (e: QuickPickItemButtonEvent) => { + if (e.item && 'filePath' in e.item) { + await window.showTextDocument(e.item.filePath, { preview: true }); + } + }, + }); -export async function validateManagePy( - folder: vscode.WorkspaceFolder | undefined, - defaultValue: string, - selected?: string, -): Promise { - const error = DebugConfigStrings.django.enterManagePyPath.invalid; - if (!selected || selected.trim().length === 0) { - return error; - } - const resolvedPath = resolveVariables(selected, undefined, folder); - if (resolvedPath) { - if (selected !== defaultValue && !(await fs.pathExists(resolvedPath))) { - return error; - } - if (!resolvedPath.trim().toLowerCase().endsWith('.py')) { - return error; + if (selection === undefined) { + return; + } else if (selection.label === browseFileOption.label) { + const uris = await openFileExplorer(state.folder?.uri); + if (uris && uris.length > 0) { + config.program = parseManagePyPath(state.folder, uris[0].fsPath); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + browsefilevalue: true, + }); + } else { + return Promise.reject(InputFlowAction.resume); } + } else if (typeof selection === 'string') { + config.program = selection; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + manuallyEnteredAValue: true, + }); + } else { + config.program = selection.description; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + autoDetectedDjangoManagePyPath: true, + }); } - return undefined; + Object.assign(state.config, config); } -export async function getManagePyPath(folder: vscode.WorkspaceFolder | undefined): Promise { +export function parseManagePyPath(folder: WorkspaceFolder | undefined, djangoPath: string): string | undefined { if (!folder) { - return undefined; + return djangoPath; } - const defaultLocationOfManagePy = path.join(folder.uri.fsPath, 'manage.py'); - if (await fs.pathExists(defaultLocationOfManagePy)) { - return `${workspaceFolderToken}${path.sep}manage.py`; + const baseManagePath = path.relative(folder.uri.fsPath, djangoPath); + if (baseManagePath) { + return `${workspaceFolderToken}${path.sep}${baseManagePath}`; + } else { + return djangoPath; } - return undefined; } diff --git a/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts b/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts new file mode 100644 index 00000000..e8e563c4 --- /dev/null +++ b/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { QuickInputButton, ThemeIcon, Uri, window } from 'vscode'; +import { browsePath } from '../../../../common/utils/localize'; +import { OSType, getOSType } from '../../../../common/platform'; + +export const goToFileButton: QuickInputButton = { + iconPath: new ThemeIcon('go-to-file'), + tooltip: `Open in Preview`, +}; + +export const browseFileOption = { + label: `$(folder) ${browsePath.label}`, + description: browsePath.detail, +}; + +export async function openFileExplorer(folder: Uri | undefined) { + const filtersKey = 'Executables'; + const filtersObject: { [name: string]: string[] } = {}; + filtersObject[filtersKey] = ['exe']; + return await window.showOpenDialog({ + filters: getOSType() == OSType.Windows ? filtersObject : undefined, + openLabel: browsePath.openButtonLabel, + canSelectMany: false, + title: browsePath.title, + defaultUri: folder ? folder : undefined, + }); +} diff --git a/src/extension/debugger/configuration/providers/pathQuickPick/types.ts b/src/extension/debugger/configuration/providers/pathQuickPick/types.ts new file mode 100644 index 00000000..ecac4f3b --- /dev/null +++ b/src/extension/debugger/configuration/providers/pathQuickPick/types.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { QuickPickItem, QuickPickItemKind, Uri } from 'vscode'; + +export interface PathQuickPickItem extends QuickPickItem { + filePath: Uri; + kind?: QuickPickItemKind; + description: string; +} +export interface SeparatorQuickPickItem extends QuickPickItem { + label: string; + kind?: QuickPickItemKind; +} + +export type QuickPickType = PathQuickPickItem | SeparatorQuickPickItem; diff --git a/src/extension/debugger/configuration/utils/configuration.ts b/src/extension/debugger/configuration/utils/configuration.ts index cf2c9466..67f296ab 100644 --- a/src/extension/debugger/configuration/utils/configuration.ts +++ b/src/extension/debugger/configuration/utils/configuration.ts @@ -6,7 +6,6 @@ 'use strict'; -import * as path from 'path'; import * as fs from 'fs-extra'; import { MultiStepInput } from '../../../common/multiStepInput'; import { sendTelemetryEvent } from '../../../telemetry'; @@ -47,37 +46,9 @@ export async function configurePort( }); } -// async function getPossiblePaths( -// folder: WorkspaceFolder, -// globPatterns: string[], -// regex: RegExp, -// ): Promise { -// const foundPathsPromises = (await Promise.allSettled( -// globPatterns.map( -// async (pattern): Promise => -// (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 fs.readFile(possiblePath)).toString()), -// ); - -// return finalPaths; -// } - -async function getPossiblePaths( - globPatterns: string[], - regex: RegExp, -): Promise { +async function getPossiblePaths(globPatterns: string[], regex: RegExp): Promise { const foundPathsPromises = (await Promise.allSettled( - globPatterns.map( - async (pattern): Promise => - (await workspace.findFiles(pattern)) - ), + globPatterns.map(async (pattern): Promise => await workspace.findFiles(pattern)), )) as { status: string; value: [] }[]; const possiblePaths: Uri[] = []; foundPathsPromises.forEach((result) => possiblePaths.push(...result.value)); @@ -88,15 +59,12 @@ async function getPossiblePaths( return finalPaths; } -export async function getDjangoPaths(folder: WorkspaceFolder | undefined) { +export async function getDjangoPaths(folder: WorkspaceFolder | undefined): Promise { if (!folder) { - return undefined; + return []; } const regExpression = /execute_from_command_line\(/; - const djangoPaths = await getPossiblePaths( - ['manage.py', '*/manage.py', 'app.py', '*/app.py'], - regExpression, - ); + const djangoPaths = await getPossiblePaths(['manage.py', '*/manage.py', 'app.py', '*/app.py'], regExpression); return djangoPaths; } diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 92128dfa..6040db73 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -13,7 +13,6 @@ import { ChildProcessAttachService } from './debugger/hooks/childProcessAttachSe import { PythonDebugConfigurationService } from './debugger/configuration/debugConfigurationService'; import { AttachConfigurationResolver } from './debugger/configuration/resolvers/attach'; import { LaunchConfigurationResolver } from './debugger/configuration/resolvers/launch'; -import { MultiStepInputFactory } from './common/multiStepInput'; import { sendTelemetryEvent } from './telemetry'; import { Commands } from './common/constants'; import { EventName } from './telemetry/constants'; @@ -47,11 +46,10 @@ export async function registerDebugger(context: IExtensionContext): Promise Date: Mon, 18 Mar 2024 20:07:30 -0700 Subject: [PATCH 05/12] update strings --- src/extension/common/multiStepInput.ts | 1 + src/extension/common/utils/localize.ts | 20 +++++++------------ .../configuration/providers/djangoLaunch.ts | 4 ++-- .../providers/pathQuickPick/providerPicker.ts | 10 +++++----- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/extension/common/multiStepInput.ts b/src/extension/common/multiStepInput.ts index fd848c16..95d1abb9 100644 --- a/src/extension/common/multiStepInput.ts +++ b/src/extension/common/multiStepInput.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. diff --git a/src/extension/common/utils/localize.ts b/src/extension/common/utils/localize.ts index 0b2266eb..69059bcb 100644 --- a/src/extension/common/utils/localize.ts +++ b/src/extension/common/utils/localize.ts @@ -22,6 +22,12 @@ export namespace DebugConfigStrings { label: l10n.t('Python Debugger'), description: l10n.t('Select a Python Debugger debug configuration'), }; + export const browsePath = { + label: l10n.t('Find...'), + detail: l10n.t('Browse your file system to find a Python file.'), + openButtonLabel: l10n.t('Select File'), + title: l10n.t('Select Python File'), + }; export namespace file { export const snippet = { name: l10n.t('Python Debugger: Current File'), @@ -92,12 +98,11 @@ export namespace DebugConfigStrings { label: l10n.t('Django'), description: l10n.t('Launch and debug a Django web application'), }; - export const enterManagePyPath = { + export const djangoConfigPromp = { title: l10n.t('Debug Django'), prompt: l10n.t( "Enter the path to manage.py or select one from the list ('${workspaceFolderToken}' points to the root of the current workspace folder)", ), - invalid: l10n.t('Enter a valid Python file path'), }; } export namespace fastapi { @@ -170,14 +175,3 @@ export namespace pickArgsInput { export const title = l10n.t('Command Line Arguments'); export const prompt = l10n.t('Enter the command line arguments you want to pass to the program'); } - -// export const providerQuickPick = { -// refreshInterpreterList -// } - -export const browsePath = { - label: l10n.t('Find...'), - detail: l10n.t('Browse your file system to find a Python file.'), - openButtonLabel: l10n.t('Select File'), - title: l10n.t('Select Python File'), -}; diff --git a/src/extension/debugger/configuration/providers/djangoLaunch.ts b/src/extension/debugger/configuration/providers/djangoLaunch.ts index a2f00d37..8e1a8d0b 100644 --- a/src/extension/debugger/configuration/providers/djangoLaunch.ts +++ b/src/extension/debugger/configuration/providers/djangoLaunch.ts @@ -69,12 +69,12 @@ export async function pickDjangoPrompt( ]; const selection = await input.showQuickPick>({ - placeholder: DebugConfigStrings.django.enterManagePyPath.prompt, + placeholder: DebugConfigStrings.django.djangoConfigPromp.prompt, items: options, acceptFilterBoxTextAsSelection: true, activeItem: options[0], matchOnDescription: true, - title: DebugConfigStrings.django.enterManagePyPath.title, + title: DebugConfigStrings.django.djangoConfigPromp.title, onDidTriggerItemButton: async (e: QuickPickItemButtonEvent) => { if (e.item && 'filePath' in e.item) { await window.showTextDocument(e.item.filePath, { preview: true }); diff --git a/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts b/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts index e8e563c4..bf776f65 100644 --- a/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts +++ b/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts @@ -2,8 +2,8 @@ // Licensed under the MIT License. import { QuickInputButton, ThemeIcon, Uri, window } from 'vscode'; -import { browsePath } from '../../../../common/utils/localize'; import { OSType, getOSType } from '../../../../common/platform'; +import { DebugConfigStrings } from '../../../../common/utils/localize'; export const goToFileButton: QuickInputButton = { iconPath: new ThemeIcon('go-to-file'), @@ -11,8 +11,8 @@ export const goToFileButton: QuickInputButton = { }; export const browseFileOption = { - label: `$(folder) ${browsePath.label}`, - description: browsePath.detail, + label: `$(folder) ${DebugConfigStrings.browsePath.label}`, + description: DebugConfigStrings.browsePath.detail, }; export async function openFileExplorer(folder: Uri | undefined) { @@ -21,9 +21,9 @@ export async function openFileExplorer(folder: Uri | undefined) { filtersObject[filtersKey] = ['exe']; return await window.showOpenDialog({ filters: getOSType() == OSType.Windows ? filtersObject : undefined, - openLabel: browsePath.openButtonLabel, + openLabel: DebugConfigStrings.browsePath.openButtonLabel, canSelectMany: false, - title: browsePath.title, + title: DebugConfigStrings.browsePath.title, defaultUri: folder ? folder : undefined, }); } From f3fae7be6b038ec390c7282dca33dec07965d912 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Tue, 19 Mar 2024 15:37:05 -0700 Subject: [PATCH 06/12] fix tests --- src/extension/common/multiStepInput.ts | 10 ++++++++++ .../configuration/debugConfigurationService.ts | 6 +++--- src/extension/extensionInit.ts | 4 +++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/extension/common/multiStepInput.ts b/src/extension/common/multiStepInput.ts index 95d1abb9..7c232bc4 100644 --- a/src/extension/common/multiStepInput.ts +++ b/src/extension/common/multiStepInput.ts @@ -320,3 +320,13 @@ export class MultiStepInput implements IMultiStepInput { } } } + +export const IMultiStepInputFactory = Symbol('IMultiStepInputFactory'); +export interface IMultiStepInputFactory { + create(): IMultiStepInput; +} +export class MultiStepInputFactory { + public create(): IMultiStepInput { + return new MultiStepInput(); + } +} \ No newline at end of file diff --git a/src/extension/debugger/configuration/debugConfigurationService.ts b/src/extension/debugger/configuration/debugConfigurationService.ts index 13edb6f6..50da3c18 100644 --- a/src/extension/debugger/configuration/debugConfigurationService.ts +++ b/src/extension/debugger/configuration/debugConfigurationService.ts @@ -7,7 +7,7 @@ import { inject, injectable, named } from 'inversify'; import { cloneDeep } from 'lodash'; import { CancellationToken, DebugConfiguration, QuickPickItem, WorkspaceFolder } from 'vscode'; import { DebugConfigStrings } from '../../common/utils/localize'; -import { InputStep, IQuickPickParameters, MultiStepInput } from '../../common/multiStepInput'; +import { IMultiStepInputFactory, InputStep, IQuickPickParameters, MultiStepInput } from '../../common/multiStepInput'; import { AttachRequestArguments, DebugConfigurationArguments, LaunchRequestArguments } from '../../types'; import { DebugConfigurationState, DebugConfigurationType, IDebugConfigurationService } from '../types'; import { buildDjangoLaunchDebugConfiguration } from './providers/djangoLaunch'; @@ -32,6 +32,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi @inject(IDebugConfigurationResolver) @named('launch') private readonly launchResolver: IDebugConfigurationResolver, + @inject(IMultiStepInputFactory) private readonly multiStepFactory: IMultiStepInputFactory, ) {} public async provideDebugConfigurations( @@ -41,8 +42,7 @@ export class PythonDebugConfigurationService implements IDebugConfigurationServi const config: Partial = {}; const state = { config, folder, token }; - // Disabled until configuration issues are addressed by VS Code. See #4007 - const multiStep = new MultiStepInput(); + const multiStep = this.multiStepFactory.create(); await multiStep.run((input, s) => PythonDebugConfigurationService.pickDebugConfiguration(input, s), state); if (Object.keys(state.config).length !== 0) { diff --git a/src/extension/extensionInit.ts b/src/extension/extensionInit.ts index 6040db73..92128dfa 100644 --- a/src/extension/extensionInit.ts +++ b/src/extension/extensionInit.ts @@ -13,6 +13,7 @@ import { ChildProcessAttachService } from './debugger/hooks/childProcessAttachSe import { PythonDebugConfigurationService } from './debugger/configuration/debugConfigurationService'; import { AttachConfigurationResolver } from './debugger/configuration/resolvers/attach'; import { LaunchConfigurationResolver } from './debugger/configuration/resolvers/launch'; +import { MultiStepInputFactory } from './common/multiStepInput'; import { sendTelemetryEvent } from './telemetry'; import { Commands } from './common/constants'; import { EventName } from './telemetry/constants'; @@ -46,10 +47,11 @@ export async function registerDebugger(context: IExtensionContext): Promise Date: Tue, 19 Mar 2024 18:06:18 -0700 Subject: [PATCH 07/12] fix tests --- src/extension/common/multiStepInput.ts | 2 +- src/extension/common/utils/localize.ts | 2 +- .../configuration/providers/djangoLaunch.ts | 86 +---------- .../djangoProviderQuickPick.ts | 82 ++++++++++ .../providerQuickPick.ts} | 0 .../types.ts | 0 .../providers/djangoLaunch.unit.test.ts | 146 +++++------------- .../djangoProviderQuickPick.unit.test.ts | 49 ++++++ 8 files changed, 176 insertions(+), 191 deletions(-) create mode 100644 src/extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick.ts rename src/extension/debugger/configuration/providers/{pathQuickPick/providerPicker.ts => providerQuickPick/providerQuickPick.ts} (100%) rename src/extension/debugger/configuration/providers/{pathQuickPick => providerQuickPick}/types.ts (100%) create mode 100644 src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts diff --git a/src/extension/common/multiStepInput.ts b/src/extension/common/multiStepInput.ts index 7c232bc4..614f7d61 100644 --- a/src/extension/common/multiStepInput.ts +++ b/src/extension/common/multiStepInput.ts @@ -329,4 +329,4 @@ export class MultiStepInputFactory { public create(): IMultiStepInput { return new MultiStepInput(); } -} \ No newline at end of file +} diff --git a/src/extension/common/utils/localize.ts b/src/extension/common/utils/localize.ts index 69059bcb..e83b365d 100644 --- a/src/extension/common/utils/localize.ts +++ b/src/extension/common/utils/localize.ts @@ -27,7 +27,7 @@ export namespace DebugConfigStrings { detail: l10n.t('Browse your file system to find a Python file.'), openButtonLabel: l10n.t('Select File'), title: l10n.t('Select Python File'), - }; + }; export namespace file { export const snippet = { name: l10n.t('Python Debugger: Current File'), diff --git a/src/extension/debugger/configuration/providers/djangoLaunch.ts b/src/extension/debugger/configuration/providers/djangoLaunch.ts index 8e1a8d0b..b5397bc7 100644 --- a/src/extension/debugger/configuration/providers/djangoLaunch.ts +++ b/src/extension/debugger/configuration/providers/djangoLaunch.ts @@ -3,20 +3,17 @@ 'use strict'; -import { QuickPick, QuickPickItemButtonEvent, QuickPickItemKind, Uri, WorkspaceFolder, window } from 'vscode'; +import { Uri } from 'vscode'; import * as path from 'path'; -import { IQuickPickParameters, InputFlowAction, MultiStepInput } from '../../../common/multiStepInput'; -import { sendTelemetryEvent } from '../../../telemetry'; -import { EventName } from '../../../telemetry/constants'; +import { MultiStepInput } from '../../../common/multiStepInput'; import { DebuggerTypeName } from '../../../constants'; import { LaunchRequestArguments } from '../../../types'; -import { DebugConfigurationState, DebugConfigurationType } from '../../types'; +import { DebugConfigurationState } from '../../types'; import { DebugConfigStrings } from '../../../common/utils/localize'; import { getDjangoPaths } from '../utils/configuration'; -import { browseFileOption, goToFileButton, openFileExplorer } from './pathQuickPick/providerPicker'; -import { QuickPickType } from './pathQuickPick/types'; - -const workspaceFolderToken = '${workspaceFolder}'; +import { goToFileButton } from './providerQuickPick/providerQuickPick'; +import { QuickPickType } from './providerQuickPick/types'; +import { parseManagePyPath, pickDjangoPrompt } from './providerQuickPick/djangoProviderQuickPick'; export async function buildDjangoLaunchDebugConfiguration( input: MultiStepInput, @@ -32,9 +29,7 @@ export async function buildDjangoLaunchDebugConfiguration( }; let djangoPaths = await getDjangoPaths(state.folder); - let options: QuickPickType[] = []; - //add found paths to options if (djangoPaths.length > 0) { options.push( @@ -53,72 +48,5 @@ export async function buildDjangoLaunchDebugConfiguration( filePath: Uri.file(managePath), }); } - await input.run((input, s) => pickDjangoPrompt(input, s, config, options), state); -} - -export async function pickDjangoPrompt( - input: MultiStepInput, - state: DebugConfigurationState, - config: Partial, - pathsOptions: QuickPickType[], -) { - let options: QuickPickType[] = [ - ...pathsOptions, - { label: '', kind: QuickPickItemKind.Separator }, - browseFileOption, - ]; - - const selection = await input.showQuickPick>({ - placeholder: DebugConfigStrings.django.djangoConfigPromp.prompt, - items: options, - acceptFilterBoxTextAsSelection: true, - activeItem: options[0], - matchOnDescription: true, - title: DebugConfigStrings.django.djangoConfigPromp.title, - onDidTriggerItemButton: async (e: QuickPickItemButtonEvent) => { - if (e.item && 'filePath' in e.item) { - await window.showTextDocument(e.item.filePath, { preview: true }); - } - }, - }); - - if (selection === undefined) { - return; - } else if (selection.label === browseFileOption.label) { - const uris = await openFileExplorer(state.folder?.uri); - if (uris && uris.length > 0) { - config.program = parseManagePyPath(state.folder, uris[0].fsPath); - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - browsefilevalue: true, - }); - } else { - return Promise.reject(InputFlowAction.resume); - } - } else if (typeof selection === 'string') { - config.program = selection; - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - manuallyEnteredAValue: true, - }); - } else { - config.program = selection.description; - sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { - configurationType: DebugConfigurationType.launchDjango, - autoDetectedDjangoManagePyPath: true, - }); - } - Object.assign(state.config, config); -} - -export function parseManagePyPath(folder: WorkspaceFolder | undefined, djangoPath: string): string | undefined { - if (!folder) { - return djangoPath; - } - const baseManagePath = path.relative(folder.uri.fsPath, djangoPath); - if (baseManagePath) { - return `${workspaceFolderToken}${path.sep}${baseManagePath}`; - } else { - return djangoPath; - } + await input.run((_input, state) => pickDjangoPrompt(input, state, config, options), state); } diff --git a/src/extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick.ts b/src/extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick.ts new file mode 100644 index 00000000..07feb0f9 --- /dev/null +++ b/src/extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick.ts @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { window, QuickPickItemButtonEvent, QuickPickItemKind, WorkspaceFolder } from 'vscode'; +import { IQuickPickParameters, InputFlowAction, MultiStepInput } from '../../../../common/multiStepInput'; +import { LaunchRequestArguments } from '../../../../types'; +import { DebugConfigurationState, DebugConfigurationType } from '../../../types'; +import { QuickPickType } from './types'; +import { browseFileOption, openFileExplorer } from './providerQuickPick'; +import { DebugConfigStrings } from '../../../../common/utils/localize'; +import { sendTelemetryEvent } from '../../../../telemetry'; +import { EventName } from '../../../../telemetry/constants'; + +export const workspaceFolderToken = '${workspaceFolder}'; + +export async function pickDjangoPrompt( + input: MultiStepInput, + state: DebugConfigurationState, + config: Partial, + pathsOptions: QuickPickType[], +) { + let options: QuickPickType[] = [ + ...pathsOptions, + { label: '', kind: QuickPickItemKind.Separator }, + browseFileOption, + ]; + + const selection = await input.showQuickPick>({ + placeholder: DebugConfigStrings.django.djangoConfigPromp.prompt, + items: options, + acceptFilterBoxTextAsSelection: true, + activeItem: options[0], + matchOnDescription: true, + title: DebugConfigStrings.django.djangoConfigPromp.title, + onDidTriggerItemButton: async (e: QuickPickItemButtonEvent) => { + if (e.item && 'filePath' in e.item) { + await window.showTextDocument(e.item.filePath, { preview: true }); + } + }, + }); + + if (selection === undefined) { + return; + } else if (selection.label === browseFileOption.label) { + const uris = await openFileExplorer(state.folder?.uri); + if (uris && uris.length > 0) { + config.program = parseManagePyPath(state.folder, uris[0].fsPath); + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + browsefilevalue: true, + }); + } else { + return Promise.reject(InputFlowAction.resume); + } + } else if (typeof selection === 'string') { + config.program = selection; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + manuallyEnteredAValue: true, + }); + } else { + config.program = selection.description; + sendTelemetryEvent(EventName.DEBUGGER_CONFIGURATION_PROMPTS, undefined, { + configurationType: DebugConfigurationType.launchDjango, + autoDetectedDjangoManagePyPath: true, + }); + } + Object.assign(state.config, config); +} + +export function parseManagePyPath(folder: WorkspaceFolder | undefined, djangoPath: string): string | undefined { + if (!folder) { + return djangoPath; + } + const baseManagePath = path.relative(folder.uri.fsPath, djangoPath); + if (baseManagePath && !baseManagePath.startsWith('..')) { + return `${workspaceFolderToken}${path.sep}${baseManagePath}`; + } else { + return djangoPath; + } +} diff --git a/src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts b/src/extension/debugger/configuration/providers/providerQuickPick/providerQuickPick.ts similarity index 100% rename from src/extension/debugger/configuration/providers/pathQuickPick/providerPicker.ts rename to src/extension/debugger/configuration/providers/providerQuickPick/providerQuickPick.ts diff --git a/src/extension/debugger/configuration/providers/pathQuickPick/types.ts b/src/extension/debugger/configuration/providers/providerQuickPick/types.ts similarity index 100% rename from src/extension/debugger/configuration/providers/pathQuickPick/types.ts rename to src/extension/debugger/configuration/providers/providerQuickPick/types.ts diff --git a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts index 60873509..17af878a 100644 --- a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts @@ -3,136 +3,62 @@ 'use strict'; -import { Uri } from 'vscode'; +import { ThemeIcon, Uri } from 'vscode'; import { expect } from 'chai'; import * as path from 'path'; -import * as fs from 'fs-extra'; +import * as typemoq from 'typemoq'; import * as sinon from 'sinon'; -import { anything, instance, mock, when } from 'ts-mockito'; -import { DebugConfigStrings } from '../../../../extension/common/utils/localize'; -import { DebuggerTypeName } from '../../../../extension/constants'; import { MultiStepInput } from '../../../../extension/common/multiStepInput'; import { DebugConfigurationState } from '../../../../extension/debugger/types'; -import { resolveVariables } from '../../../../extension/debugger/configuration/utils/common'; import * as vscodeapi from '../../../../extension/common/vscodeapi'; import * as djangoLaunch from '../../../../extension/debugger/configuration/providers/djangoLaunch'; +import * as configuration from '../../../../extension/debugger/configuration/utils/configuration'; +import * as djangoProviderQuickPick from '../../../../extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick'; suite('Debugging - Configuration Provider Django', () => { - let pathExistsStub: sinon.SinonStub; + // let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; let workspaceStub: sinon.SinonStub; - let input: MultiStepInput; + let getDjangoPathsStub: sinon.SinonStub; + let pickDjangoPromptStub: sinon.SinonStub; + let multiStepInput: typemoq.IMock>; setup(() => { - input = mock>(MultiStepInput); - pathExistsStub = sinon.stub(fs, 'pathExists'); + multiStepInput = typemoq.Mock.ofType>(); + multiStepInput + .setup((i) => i.run(typemoq.It.isAny(), typemoq.It.isAny())) + .returns((callback, _state) => callback()); pathSeparatorStub = sinon.stub(path, 'sep'); workspaceStub = sinon.stub(vscodeapi, 'getWorkspaceFolder'); + getDjangoPathsStub = sinon.stub(configuration, 'getDjangoPaths'); + pickDjangoPromptStub = sinon.stub(djangoProviderQuickPick, 'pickDjangoPrompt'); + pathSeparatorStub.value('-'); }); teardown(() => { sinon.restore(); }); - test("getManagePyPath should return undefined if file doesn't exist", async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - pathExistsStub.withArgs(managePyPath).resolves(false); - const file = await djangoLaunch.getManagePyPath(folder); - - expect(file).to.be.equal(undefined, 'Should return undefined'); - }); - test('getManagePyPath should file path', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); - pathExistsStub.withArgs(managePyPath).resolves(true); - pathSeparatorStub.value('-'); - const file = await djangoLaunch.getManagePyPath(folder); - - expect(file).to.be.equal('${workspaceFolder}-manage.py'); - }); - test('Resolve variables (with resource)', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - workspaceStub.returns(folder); - const resolvedPath = resolveVariables('${workspaceFolder}/one.py', undefined, folder); - - expect(resolvedPath).to.be.equal(`${folder.uri.fsPath}/one.py`); - }); - test('Validation of path should return errors if path is undefined', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await djangoLaunch.validateManagePy(folder, ''); - - expect(error).to.be.length.greaterThan(1); - }); - test('Validation of path should return errors if path is empty', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await djangoLaunch.validateManagePy(folder, '', ''); - - expect(error).to.be.length.greaterThan(1); - }); - test('Validation of path should return errors if resolved path is empty', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const error = await djangoLaunch.validateManagePy(folder, '', 'x'); - - expect(error).to.be.length.greaterThan(1); - }); - test("Validation of path should return errors if resolved path doesn't exist", async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - pathExistsStub.withArgs('xyz').resolves(false); - const error = await djangoLaunch.validateManagePy(folder, '', 'x'); - - expect(error).to.be.length.greaterThan(1); - }); - test('Validation of path should return errors if resolved path is non-python', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - pathExistsStub.withArgs('xyz.txt').resolves(true); - const error = await djangoLaunch.validateManagePy(folder, '', 'x'); - - expect(error).to.be.length.greaterThan(1); - }); - test('Validation of path should return errors if resolved path is python', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - pathExistsStub.withArgs('xyz.py').resolves(true); - const error = await djangoLaunch.validateManagePy(folder, '', 'xyz.py'); - - expect(error).to.be.equal(undefined, 'should not have errors'); - }); - test('Launch JSON with selected managepy path', async () => { + test('Show picker and send parsed managepy paths', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve('hello'); - await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: 'hello', - args: ['runserver'], - django: true, - autoStartBrowser: false, - }; - - expect(state.config).to.be.deep.equal(config); - }); - test('Launch JSON with default managepy path', async () => { - const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; - const state = { config: {}, folder }; - const workspaceFolderToken = '${workspaceFolder}'; - const defaultProgram = `${workspaceFolderToken}-manage.py`; - pathSeparatorStub.value('-'); - when(input.showInputBox(anything())).thenResolve(defaultProgram); - await djangoLaunch.buildDjangoLaunchDebugConfiguration(instance(input), state); - - const config = { - name: DebugConfigStrings.django.snippet.name, - type: DebuggerTypeName, - request: 'launch', - program: defaultProgram, - args: ['runserver'], - django: true, - autoStartBrowser: false, - }; - - expect(state.config).to.be.deep.equal(config); + const managePath = Uri.parse(path.join(folder.uri.fsPath, 'manage.py')); + getDjangoPathsStub.resolves([managePath]); + pickDjangoPromptStub.resolves(); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(multiStepInput.object, state); + const options = pickDjangoPromptStub.getCall(0).args[3]; + const expectedOptions = [ + { + label: path.basename(managePath.fsPath), + filePath: managePath, + description: `${djangoProviderQuickPick.workspaceFolderToken}-manage.py`, + buttons: [ + { + iconPath: new ThemeIcon('go-to-file'), + tooltip: `Open in Preview`, + }, + ], + }, + ]; + + expect(options).to.be.deep.equal(expectedOptions); }); }); diff --git a/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts b/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts new file mode 100644 index 00000000..297df6ea --- /dev/null +++ b/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Uri } from 'vscode'; +import { expect } from 'chai'; +import * as path from 'path'; +import * as typemoq from 'typemoq'; +import * as sinon from 'sinon'; +import { MultiStepInput } from '../../../../../extension/common/multiStepInput'; +import { DebugConfigurationState } from '../../../../../extension/debugger/types'; +import { + parseManagePyPath, + workspaceFolderToken, +} from '../../../../../extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick'; + +suite('Debugging - Configuration Provider Django QuickPick', () => { + // let pathExistsStub: sinon.SinonStub; + let pathSeparatorStub: sinon.SinonStub; + let multiStepInput: typemoq.IMock>; + + setup(() => { + multiStepInput = typemoq.Mock.ofType>(); + multiStepInput + .setup((i) => i.run(typemoq.It.isAny(), typemoq.It.isAny())) + .returns((callback, _state) => callback()); + pathSeparatorStub = sinon.stub(path, 'sep'); + pathSeparatorStub.value('-'); + }); + teardown(() => { + sinon.restore(); + }); + test('parseManagePyPath should parse the path and return it with workspaceFolderToken', () => { + const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; + const managePyPath = path.join(folder.uri.fsPath, 'manage.py'); + const file = parseManagePyPath(folder, managePyPath); + pathSeparatorStub.value('-'); + const expectedValue = `${workspaceFolderToken}-manage.py`; + expect(file).to.be.equal(expectedValue); + }); + test('parseManagePyPath should return the same path if the workspace do not match', () => { + const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; + const managePyPath = 'random/path/manage.py'; + const file = parseManagePyPath(folder, managePyPath); + + expect(file).to.be.equal(managePyPath); + }); +}); From a32e7c4a7799c65776878bd9942c436f13ced563 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Tue, 19 Mar 2024 18:13:49 -0700 Subject: [PATCH 08/12] fix lint errorr and text --- src/extension/common/utils/async.ts | 2 +- src/extension/common/utils/localize.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extension/common/utils/async.ts b/src/extension/common/utils/async.ts index 516fbf1d..ca8c8d52 100644 --- a/src/extension/common/utils/async.ts +++ b/src/extension/common/utils/async.ts @@ -11,7 +11,7 @@ export interface Deferred { readonly rejected: boolean; readonly completed: boolean; resolve(value?: T | PromiseLike): void; - reject(reason?: string | Error | Record): void; + reject(reason?: string | Error | Record | unknown): void; } class DeferredImpl implements Deferred { diff --git a/src/extension/common/utils/localize.ts b/src/extension/common/utils/localize.ts index e83b365d..5ed37ca7 100644 --- a/src/extension/common/utils/localize.ts +++ b/src/extension/common/utils/localize.ts @@ -23,7 +23,7 @@ export namespace DebugConfigStrings { description: l10n.t('Select a Python Debugger debug configuration'), }; export const browsePath = { - label: l10n.t('Find...'), + label: l10n.t('Browse Files...'), detail: l10n.t('Browse your file system to find a Python file.'), openButtonLabel: l10n.t('Select File'), title: l10n.t('Select Python File'), @@ -101,7 +101,7 @@ export namespace DebugConfigStrings { export const djangoConfigPromp = { title: l10n.t('Debug Django'), prompt: l10n.t( - "Enter the path to manage.py or select one from the list ('${workspaceFolderToken}' points to the root of the current workspace folder)", + "Enter the path to manage.py or select a file from the list ('${workspaceFolderToken}' points to the root of the current workspace folder)", ), }; } From f81403f57b24334ed8d23386c01a7efc832e2708 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Tue, 19 Mar 2024 18:16:18 -0700 Subject: [PATCH 09/12] fix lint in tests --- .../unittest/configuration/providers/djangoLaunch.unit.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts index 17af878a..12ca144c 100644 --- a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts @@ -10,7 +10,6 @@ import * as typemoq from 'typemoq'; import * as sinon from 'sinon'; import { MultiStepInput } from '../../../../extension/common/multiStepInput'; import { DebugConfigurationState } from '../../../../extension/debugger/types'; -import * as vscodeapi from '../../../../extension/common/vscodeapi'; import * as djangoLaunch from '../../../../extension/debugger/configuration/providers/djangoLaunch'; import * as configuration from '../../../../extension/debugger/configuration/utils/configuration'; import * as djangoProviderQuickPick from '../../../../extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick'; @@ -18,7 +17,6 @@ import * as djangoProviderQuickPick from '../../../../extension/debugger/configu suite('Debugging - Configuration Provider Django', () => { // let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; - let workspaceStub: sinon.SinonStub; let getDjangoPathsStub: sinon.SinonStub; let pickDjangoPromptStub: sinon.SinonStub; let multiStepInput: typemoq.IMock>; @@ -29,7 +27,6 @@ suite('Debugging - Configuration Provider Django', () => { .setup((i) => i.run(typemoq.It.isAny(), typemoq.It.isAny())) .returns((callback, _state) => callback()); pathSeparatorStub = sinon.stub(path, 'sep'); - workspaceStub = sinon.stub(vscodeapi, 'getWorkspaceFolder'); getDjangoPathsStub = sinon.stub(configuration, 'getDjangoPaths'); pickDjangoPromptStub = sinon.stub(djangoProviderQuickPick, 'pickDjangoPrompt'); pathSeparatorStub.value('-'); From 34e0862cd433425d92ebd55520c6d8d5ff113b59 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 20 Mar 2024 12:33:42 -0700 Subject: [PATCH 10/12] Add default option --- .../configuration/providers/djangoLaunch.ts | 2 +- .../providers/djangoLaunch.unit.test.ts | 24 ++++++++++++++++++- src/test/unittest/index.ts | 2 +- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/extension/debugger/configuration/providers/djangoLaunch.ts b/src/extension/debugger/configuration/providers/djangoLaunch.ts index b5397bc7..bff23756 100644 --- a/src/extension/debugger/configuration/providers/djangoLaunch.ts +++ b/src/extension/debugger/configuration/providers/djangoLaunch.ts @@ -43,7 +43,7 @@ export async function buildDjangoLaunchDebugConfiguration( } else { const managePath = path.join(state?.folder?.uri.fsPath || '', 'manage.py'); options.push({ - label: 'manage.py', + label: 'Default', description: parseManagePyPath(state.folder, managePath), filePath: Uri.file(managePath), }); diff --git a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts index 12ca144c..a0e42d8b 100644 --- a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts @@ -34,10 +34,12 @@ suite('Debugging - Configuration Provider Django', () => { teardown(() => { sinon.restore(); }); - test('Show picker and send parsed managepy paths', async () => { + test('Show picker and send parsed found managepy paths', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; const managePath = Uri.parse(path.join(folder.uri.fsPath, 'manage.py')); + console.log('Folder:', folder.uri.fsPath); + console.log('managePath: ', managePath); getDjangoPathsStub.resolves([managePath]); pickDjangoPromptStub.resolves(); await djangoLaunch.buildDjangoLaunchDebugConfiguration(multiStepInput.object, state); @@ -56,6 +58,26 @@ suite('Debugging - Configuration Provider Django', () => { }, ]; + expect(options).to.be.deep.equal(expectedOptions); + }); + test('Show picker and send defauge managepy path', async () => { + const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; + const state = { config: {}, folder }; + const managePath = path.join(state?.folder?.uri.fsPath, 'manage.py'); + console.log('Folder:', folder.uri.fsPath); + console.log('managePath: ', managePath); + getDjangoPathsStub.resolves([]); + pickDjangoPromptStub.resolves(); + await djangoLaunch.buildDjangoLaunchDebugConfiguration(multiStepInput.object, state); + const options = pickDjangoPromptStub.getCall(0).args[3]; + const expectedOptions = [ + { + label: 'Default', + filePath: Uri.file(managePath), + description: `${djangoProviderQuickPick.workspaceFolderToken}-manage.py`, + }, + ]; + expect(options).to.be.deep.equal(expectedOptions); }); }); diff --git a/src/test/unittest/index.ts b/src/test/unittest/index.ts index 5105ed04..d699d6f4 100644 --- a/src/test/unittest/index.ts +++ b/src/test/unittest/index.ts @@ -15,7 +15,7 @@ export function run(): Promise { const testsRoot = path.resolve(__dirname); return new Promise((c, e) => { - glob('**/*.unit.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { + glob('**/providers/**/*.unit.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { if (err) { return e(err); } From fb591203aacbe77149a0cbfcf076da5ed7eb9443 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 20 Mar 2024 12:49:25 -0700 Subject: [PATCH 11/12] fix tests --- .../unittest/configuration/providers/djangoLaunch.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts index a0e42d8b..1a346f79 100644 --- a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts @@ -37,7 +37,7 @@ suite('Debugging - Configuration Provider Django', () => { test('Show picker and send parsed found managepy paths', async () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; - const managePath = Uri.parse(path.join(folder.uri.fsPath, 'manage.py')); + const managePath = Uri.file(path.join(folder.uri.fsPath, 'manage.py')); console.log('Folder:', folder.uri.fsPath); console.log('managePath: ', managePath); getDjangoPathsStub.resolves([managePath]); From 72b9d257fa8ef1886b132aab140ca8b21d89ca80 Mon Sep 17 00:00:00 2001 From: Paula Camargo Date: Wed, 20 Mar 2024 13:02:21 -0700 Subject: [PATCH 12/12] fix code --- .../configuration/providers/djangoLaunch.unit.test.ts | 5 ----- .../providerQuickPick/djangoProviderQuickPick.unit.test.ts | 1 - src/test/unittest/index.ts | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts index 1a346f79..fd287328 100644 --- a/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts +++ b/src/test/unittest/configuration/providers/djangoLaunch.unit.test.ts @@ -15,7 +15,6 @@ import * as configuration from '../../../../extension/debugger/configuration/uti import * as djangoProviderQuickPick from '../../../../extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick'; suite('Debugging - Configuration Provider Django', () => { - // let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; let getDjangoPathsStub: sinon.SinonStub; let pickDjangoPromptStub: sinon.SinonStub; @@ -38,8 +37,6 @@ suite('Debugging - Configuration Provider Django', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; const managePath = Uri.file(path.join(folder.uri.fsPath, 'manage.py')); - console.log('Folder:', folder.uri.fsPath); - console.log('managePath: ', managePath); getDjangoPathsStub.resolves([managePath]); pickDjangoPromptStub.resolves(); await djangoLaunch.buildDjangoLaunchDebugConfiguration(multiStepInput.object, state); @@ -64,8 +61,6 @@ suite('Debugging - Configuration Provider Django', () => { const folder = { uri: Uri.parse(path.join('one', 'two')), name: '1', index: 0 }; const state = { config: {}, folder }; const managePath = path.join(state?.folder?.uri.fsPath, 'manage.py'); - console.log('Folder:', folder.uri.fsPath); - console.log('managePath: ', managePath); getDjangoPathsStub.resolves([]); pickDjangoPromptStub.resolves(); await djangoLaunch.buildDjangoLaunchDebugConfiguration(multiStepInput.object, state); diff --git a/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts b/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts index 297df6ea..c268c340 100644 --- a/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts +++ b/src/test/unittest/configuration/providers/providerQuickPick/djangoProviderQuickPick.unit.test.ts @@ -16,7 +16,6 @@ import { } from '../../../../../extension/debugger/configuration/providers/providerQuickPick/djangoProviderQuickPick'; suite('Debugging - Configuration Provider Django QuickPick', () => { - // let pathExistsStub: sinon.SinonStub; let pathSeparatorStub: sinon.SinonStub; let multiStepInput: typemoq.IMock>; diff --git a/src/test/unittest/index.ts b/src/test/unittest/index.ts index d699d6f4..5105ed04 100644 --- a/src/test/unittest/index.ts +++ b/src/test/unittest/index.ts @@ -15,7 +15,7 @@ export function run(): Promise { const testsRoot = path.resolve(__dirname); return new Promise((c, e) => { - glob('**/providers/**/*.unit.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { + glob('**/*.unit.test.js', { cwd: testsRoot }, (err: any, files: any[]) => { if (err) { return e(err); }