|  | 
|  | 1 | +// Copyright (c) Microsoft Corporation. All rights reserved. | 
|  | 2 | +// Licensed under the MIT License. | 
|  | 3 | + | 
|  | 4 | +'use strict'; | 
|  | 5 | + | 
|  | 6 | +import { inject, injectable } from 'inversify'; | 
|  | 7 | +import * as path from 'path'; | 
|  | 8 | +import { DiagnosticSeverity, WorkspaceFolder } from 'vscode'; | 
|  | 9 | +import { ICommandManager, IWorkspaceService } from '../../../common/application/types'; | 
|  | 10 | +import '../../../common/extensions'; | 
|  | 11 | +import { IFileSystem } from '../../../common/platform/types'; | 
|  | 12 | +import { IServiceContainer } from '../../../ioc/types'; | 
|  | 13 | +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; | 
|  | 14 | +import { IDiagnosticsCommandFactory } from '../commands/types'; | 
|  | 15 | +import { DiagnosticCodes } from '../constants'; | 
|  | 16 | +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; | 
|  | 17 | +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; | 
|  | 18 | + | 
|  | 19 | +const InvalidDebuggerTypeMessage = 'Your launch.json file needs to be updated to change the "pythonExperimental" debug ' + | 
|  | 20 | + 'configurations to use the "python" debugger type, otherwise Python debugging may ' + | 
|  | 21 | + 'not work. Would you like to automatically update your launch.json file now?'; | 
|  | 22 | + | 
|  | 23 | +export class InvalidDebuggerTypeDiagnostic extends BaseDiagnostic { | 
|  | 24 | + constructor(message) { | 
|  | 25 | + super(DiagnosticCodes.InvalidDebuggerTypeDiagnostic, | 
|  | 26 | + message, DiagnosticSeverity.Error, DiagnosticScope.WorkspaceFolder); | 
|  | 27 | + } | 
|  | 28 | +} | 
|  | 29 | + | 
|  | 30 | +export const InvalidDebuggerTypeDiagnosticsServiceId = 'InvalidDebuggerTypeDiagnosticsServiceId'; | 
|  | 31 | + | 
|  | 32 | +const CommandName = 'python.debugger.replaceExperimental'; | 
|  | 33 | + | 
|  | 34 | +@injectable() | 
|  | 35 | +export class InvalidDebuggerTypeDiagnosticsService extends BaseDiagnosticsService { | 
|  | 36 | + protected readonly messageService: IDiagnosticHandlerService<MessageCommandPrompt>; | 
|  | 37 | + protected readonly fs: IFileSystem; | 
|  | 38 | + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { | 
|  | 39 | + super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer); | 
|  | 40 | + this.messageService = serviceContainer.get<IDiagnosticHandlerService<MessageCommandPrompt>>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); | 
|  | 41 | + const cmdManager = serviceContainer.get<ICommandManager>(ICommandManager); | 
|  | 42 | + this.fs = this.serviceContainer.get<IFileSystem>(IFileSystem); | 
|  | 43 | + cmdManager.registerCommand(CommandName, this.fixLaunchJson, this); | 
|  | 44 | + } | 
|  | 45 | + public async diagnose(): Promise<IDiagnostic[]> { | 
|  | 46 | + if (await this.isExperimentalDebuggerUsed()) { | 
|  | 47 | + return [new InvalidDebuggerTypeDiagnostic(InvalidDebuggerTypeMessage)]; | 
|  | 48 | + } else { | 
|  | 49 | + return []; | 
|  | 50 | + } | 
|  | 51 | + } | 
|  | 52 | + public async handle(diagnostics: IDiagnostic[]): Promise<void> { | 
|  | 53 | + // This class can only handle one type of diagnostic, hence just use first item in list. | 
|  | 54 | + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { | 
|  | 55 | + return; | 
|  | 56 | + } | 
|  | 57 | + const diagnostic = diagnostics[0]; | 
|  | 58 | + const commandFactory = this.serviceContainer.get<IDiagnosticsCommandFactory>(IDiagnosticsCommandFactory); | 
|  | 59 | + const options = [ | 
|  | 60 | + { | 
|  | 61 | + prompt: 'Yes, update launch.json', | 
|  | 62 | + command: commandFactory.createCommand(diagnostic, { type: 'executeVSCCommand', options: 'python.debugger.replaceExperimental' }) | 
|  | 63 | + }, | 
|  | 64 | + { | 
|  | 65 | + prompt: 'No, I will do it later' | 
|  | 66 | + } | 
|  | 67 | + ]; | 
|  | 68 | + | 
|  | 69 | + await this.messageService.handle(diagnostic, { commandPrompts: options }); | 
|  | 70 | + } | 
|  | 71 | + private async isExperimentalDebuggerUsed() { | 
|  | 72 | + const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService); | 
|  | 73 | + if (!workspaceService.hasWorkspaceFolders) { | 
|  | 74 | + return false; | 
|  | 75 | + } | 
|  | 76 | + | 
|  | 77 | + const results = await Promise.all(workspaceService.workspaceFolders!.map(workspaceFolder => this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder))); | 
|  | 78 | + return results.filter(used => used === true).length > 0; | 
|  | 79 | + } | 
|  | 80 | + private getLaunchJsonFile(workspaceFolder: WorkspaceFolder) { | 
|  | 81 | + return path.join(workspaceFolder.uri.fsPath, '.vscode', 'launch.json'); | 
|  | 82 | + } | 
|  | 83 | + private async isExperimentalDebuggerUsedInWorkspace(workspaceFolder: WorkspaceFolder) { | 
|  | 84 | + const launchJson = this.getLaunchJsonFile(workspaceFolder); | 
|  | 85 | + if (!await this.fs.fileExists(launchJson)) { | 
|  | 86 | + return false; | 
|  | 87 | + } | 
|  | 88 | + | 
|  | 89 | + const fileContents = await this.fs.readFile(launchJson); | 
|  | 90 | + return fileContents.indexOf('"pythonExperimental"') > 0; | 
|  | 91 | + } | 
|  | 92 | + private async fixLaunchJson() { | 
|  | 93 | + const workspaceService = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService); | 
|  | 94 | + if (!workspaceService.hasWorkspaceFolders) { | 
|  | 95 | + return false; | 
|  | 96 | + } | 
|  | 97 | + | 
|  | 98 | + await Promise.all(workspaceService.workspaceFolders!.map(workspaceFolder => this.fixLaunchJsonInWorkspace(workspaceFolder))); | 
|  | 99 | + } | 
|  | 100 | + private async fixLaunchJsonInWorkspace(workspaceFolder: WorkspaceFolder) { | 
|  | 101 | + if (!await this.isExperimentalDebuggerUsedInWorkspace(workspaceFolder)) { | 
|  | 102 | + return; | 
|  | 103 | + } | 
|  | 104 | + | 
|  | 105 | + const launchJson = this.getLaunchJsonFile(workspaceFolder); | 
|  | 106 | + let fileContents = await this.fs.readFile(launchJson); | 
|  | 107 | + const debuggerType = new RegExp('"pythonExperimental"', 'g'); | 
|  | 108 | + const debuggerLabel = new RegExp('"Python Experimental:', 'g'); | 
|  | 109 | + | 
|  | 110 | + fileContents = fileContents.replace(debuggerType, '"python"'); | 
|  | 111 | + fileContents = fileContents.replace(debuggerLabel, '"Python:'); | 
|  | 112 | + | 
|  | 113 | + await this.fs.writeFile(launchJson, fileContents); | 
|  | 114 | + } | 
|  | 115 | +} | 
0 commit comments