'use strict' ; import { window , workspace , commands , OutputChannel , Uri , MessageItem , ExtensionContext , ProgressLocation , EventEmitter , Event , CancellationToken , Progress , QuickPickItem , TextDocument , Disposable } from 'vscode' ; import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper" ; import { PlannerAsyncService , AsyncServiceConfiguration } from "pddl-planning-service-client" ; import * as path from 'path' ; import { dirname } from 'path' ; import { PddlWorkspace , parser , planner , ProblemInfo , Plan , DomainInfo , PddlLanguage , utils } from 'pddl-workspace' ; import { PddlConfiguration , PDDL_CONFIGURE_COMMAND , PDDL_PLANNER } from '../configuration/configuration' ; import { PlannerExecutable } from './PlannerExecutable' ; import { SAuthentication } from '../util/Authentication' ; import { PlanningResult } from './PlanningResult' ; import { PlanExporter } from './PlanExporter' ; import { PlanHappeningsExporter } from './PlanHappeningsExporter' ; import { HappeningsPlanExporter } from './HappeningsPlanExporter' ; import { isHappenings , isPlan , selectFile , isPddl } from '../workspace/workspaceUtils' ; import { PlanView , PDDL_EXPORT_PLAN } from '../planView/PlanView' ; import { PlannerUserOptionsSelector } from './PlannerUserOptionsSelector' ; import { PlannerConfigurationSelector } from './PlannerConfigurationSelector' ; import { AssociationProvider } from '../workspace/AssociationProvider' ; import { showError , isHttp } from '../utils' ; import { CodePddlWorkspace } from '../workspace/CodePddlWorkspace' ; import { EXECUTION_TARGET , PDDL_CONFIGURE_PLANNER_OUTPUT_TARGET , PlannersConfiguration } from '../configuration/PlannersConfiguration' ; import { RequestServicePlannerProvider , SolveServicePlannerProvider } from '../configuration/plannerConfigurations' ; const PDDL_STOP_PLANNER = 'pddl.stopPlanner' ; const PDDL_CONVERT_PLAN_TO_HAPPENINGS = 'pddl.convertPlanToHappenings' ; const PDDL_CONVERT_HAPPENINGS_TO_PLAN = 'pddl.convertHappeningsToPlan' ; export const PDDL_PLAN_AND_DISPLAY = 'pddl.planAndDisplayResult' ; /** Structure that VS Code passes, when user right-clicks on editor tab. */ interface EditorId { groupId : number ; editorIndex : number ; } function instanceOfEditorId ( obj ) { return 'groupId' in obj ; } /**
 * Delegate for handling requests to run the planner and visualize the plans.
 */ export class Planning implements planner . PlannerResponseHandler { output : OutputChannel ; planner : planner . Planner | null = null ; plans : Plan [ ] = [ ] ; planningProcessKilled = false ; private planView : PlanView ; optionProviders : planner . PlannerOptionsProvider [ ] = [ ] ; userOptionsProvider : PlannerUserOptionsSelector ; constructor ( private codePddlWorkspace , private pddlConfiguration , private plannersConfiguration , private context ) { this . userOptionsProvider = new PlannerUserOptionsSelector ( ) ; this . output = window . createOutputChannel ( "Planner output" ) ; context . subscriptions . push ( this . planView = new PlanView ( context , codePddlWorkspace ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_PLAN_AND_DISPLAY , async ( domainUri , payload , workingFolder , options ? ) => { if ( payload && instanceOfEditorId ( payload ) ) { await this . planFromDocument ( await workspace . openTextDocument ( domainUri ) ) . catch ( showError ) ; } else if ( Array . isArray ( payload ) ) { const selectedFiles = payload ; this . planByUris ( selectedFiles , workingFolder , options ) . catch ( showError ) ; } else if ( payload ) { await this . planByUri ( domainUri , payload , workingFolder , options ) . catch ( showError ) ; } else { await this . plan ( ) . catch ( showError ) ; } } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_STOP_PLANNER , ( ) => this . stopPlanner ( ) ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_EXPORT_PLAN , ( plan ) => { if ( plan ) { new PlanExporter ( plan ) . export ( ) ; } else { window . showErrorMessage ( "There is no plan open, or the selected plan does not exist." ) ; } } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_CONVERT_PLAN_TO_HAPPENINGS , async ( ) => { if ( window . activeTextEditor && isPlan ( window . activeTextEditor . document ) ) { const epsilon = pddlConfiguration . getEpsilonTimeStep ( ) ; new PlanHappeningsExporter ( window . activeTextEditor . document , epsilon ) . export ( ) ; } else { window . showErrorMessage ( "Active document is not a plan." ) ; } } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_CONVERT_HAPPENINGS_TO_PLAN , async ( ) => { if ( window . activeTextEditor && isHappenings ( window . activeTextEditor . document ) ) { const epsilon = pddlConfiguration . getEpsilonTimeStep ( ) ; new HappeningsPlanExporter ( window . activeTextEditor . document , epsilon ) . export ( ) ; } else { window . showErrorMessage ( "Active document is not a happening." ) ; } } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( "pddl.syntaxTree" , ( ) => { if ( window . activeTextEditor && isPddl ( window . activeTextEditor . document ) ) { const index = window . activeTextEditor . document . offsetAt ( window . activeTextEditor . selection . active ) ; const pddlSyntaxTreeBuilder = new parser . PddlSyntaxTreeBuilder ( window . activeTextEditor . document . getText ( ) ) ; this . output . appendLine ( '' ) ; this . output . appendLine ( "PDDL Syntax Tree:" ) ; this . output . appendLine ( pddlSyntaxTreeBuilder . getTreeAsString ( ) ) ; const breadcrumbs = pddlSyntaxTreeBuilder . getBreadcrumbs ( index ) ; this . output . appendLine ( '' ) ; this . output . appendLine ( "PDDL Parser Breadcrumbs:" ) ; breadcrumbs . forEach ( b => this . output . appendLine ( b . toString ( ) ) ) ; this . output . show ( ) ; } } ) ) ; context . subscriptions . push ( instrumentOperationAsVsCodeCommand ( PDDL_CONFIGURE_PLANNER_OUTPUT_TARGET , ( ) => commands . executeCommand ( PDDL_CONFIGURE_COMMAND , PDDL_PLANNER + '.' + EXECUTION_TARGET ) ) ) ; } addOptionsProvider ( optionsProvider ) { this . optionProviders . push ( optionsProvider ) ; } providePlannerOptions ( context ) { return this . optionProviders . map ( provider => provider . providePlannerOptions ( context ) ) ; } param domainUri param problemUri param workingFolder param options async planByUri ( domainUri , problemUri , workingFolder , options ? ) { const domainDocument = await workspace . openTextDocument ( domainUri ) ; const problemDocument = await workspace . openTextDocument ( problemUri ) ; const domainInfo = await this . codePddlWorkspace . upsertAndParseFile ( domainDocument ) as DomainInfo ; const problemInfo = await this . codePddlWorkspace . upsertAndParseFile ( problemDocument ) as ProblemInfo ; this . planExplicit ( domainInfo , problemInfo , workingFolder , options ) ; } async planByUris ( selectedFiles , workingFolder , options ? ) { if ( selectedFiles . length === 1 ) { await this . planFromDocument ( await workspace . openTextDocument ( selectedFiles [ 0 ] ) ) . catch ( showError ) ; } else if ( selectedFiles . length === 2 ) { let problemFileInfo ; let domainFileInfo ; for ( let i = 0 ; i < selectedFiles . length ; i ++ ) { const selectedFile = selectedFiles [ i ] ; const selectedDoc = await workspace . openTextDocument ( selectedFile ) ; const fileInfo = await this . codePddlWorkspace . upsertAndParseFile ( selectedDoc ) ; if ( fileInfo === undefined ) { throw new Error ( `Selected file is not a PDDL document: ${ selectedDoc . fileName }` ) ; } if ( fileInfo . isDomain ( ) ) { if ( domainFileInfo !== undefined ) { throw new Error ( 'Two domain files were selected. Select a domain file and one problem file.' ) ; } domainFileInfo = fileInfo as DomainInfo ; } else if ( fileInfo . isProblem ( ) ) { if ( problemFileInfo !== undefined ) { throw new Error ( 'Two problem files were selected. Select a domain file and one problem file.' ) ; } problemFileInfo = fileInfo as ProblemInfo ; } else { throw new Error ( `File not supported: ${ fileInfo . name }. Select a domain file and one problem file.` ) ; } } if ( ! domainFileInfo || ! problemFileInfo ) { throw new Error ( 'Select a domain file and one problem file.' ) ; } await this . planExplicit ( domainFileInfo , problemFileInfo , workingFolder , options ) ; } } /**
     * Invokes the planner in the context of the currently opened files in the workspace.
     */ async plan ( ) { if ( this . planner ) { window . showErrorMessage ( "Planner is already running. Stop it using the Cancel button in the progress notification, or using the 'PDDL: Stop planner' command or wait for it to finish." ) ; return ; } if ( ! window . activeTextEditor ) { window . showErrorMessage ( "Active document is not a PDDL file." ) ; return ; } const activeDocument = window . activeTextEditor . document ; return this . planFromDocument ( activeDocument ) ; } async planFromDocument ( activeDocument ) { if ( ! activeDocument ) { return ; } const activeFileInfo = await this . codePddlWorkspace . upsertAndParseFile ( activeDocument ) ; if ( activeFileInfo === undefined ) { throw new Error ( 'Selected file is not a PDDL document.' ) ; } let problemFileInfo ; let domainFileInfo ; this . output . clear ( ) ; if ( activeFileInfo . isProblem ( ) ) { problemFileInfo = activeFileInfo as ProblemInfo ; const domainFiles = this . codePddlWorkspace . getDomainFilesFor ( problemFileInfo ) ; if ( domainFiles . length === 1 ) { domainFileInfo = domainFiles [ 0 ] ; } else if ( domainFiles . length !== 1 ) { const workspaceFolder = workspace . getWorkspaceFolder ( activeDocument . uri ) ; const domainFileUri = await selectFile ( { language : PddlLanguage . PDDL , promptMessage : 'Select the matching domain file...' , findPattern : AssociationProvider . PDDL_PATTERN , fileOpenLabel : 'Select' , fileOpenFilters : { 'PDDL Domain Files' : [ 'pddl' ] } , workspaceFolder : workspaceFolder } , domainFiles ) ; if ( ! domainFileUri ) { return ; } domainFileInfo = domainFiles . find ( doc => doc . fileUri . toString ( ) === domainFileUri . toString ( ) ) || this . codePddlWorkspace . pddlWorkspace . getFileInfo < DomainInfo > ( domainFileUri ) || await this . parseDomain ( domainFileUri ) ; } else { window . showInformationMessage ( `Ensure a domain '${ problemFileInfo . domainName }' from the same folder is open in the editor.` ) ; return ; } } else if ( activeFileInfo . isDomain ( ) ) { domainFileInfo = activeFileInfo as DomainInfo ; const problemFiles = this . codePddlWorkspace . pddlWorkspace . getProblemFiles ( domainFileInfo ) ; if ( problemFiles . length === 1 ) { problemFileInfo = problemFiles [ 0 ] ; } else if ( problemFiles . length > 1 ) { const problemFilePicks = problemFiles . map ( info => new ProblemFilePickItem ( info ) ) ; const selectedProblemFilePick = await window . showQuickPick ( problemFilePicks , { placeHolder : "Select problem file:" } ) ; if ( ! selectedProblemFilePick ) { return ; } problemFileInfo = selectedProblemFilePick . problemInfo ; } else { window . showInformationMessage ( "Ensure a corresponding problem file is open in the editor." ) ; return ; } } else { window . showInformationMessage ( "Selected file does not appear to be a valid PDDL domain or problem file." ) ; return ; } const workingDirectory = this . establishWorkingDirectory ( activeDocument , problemFileInfo , domainFileInfo ) ; await this . planExplicit ( domainFileInfo , problemFileInfo , workingDirectory ) ; } private readonly _onPlansFound = new EventEmitter < PlanningResult > ( ) ; public get onPlansFound ( ) : Event < PlanningResult > { return this . _onPlansFound . event ; } private progressUpdater : ElapsedTimeProgressUpdater | undefined ; private establishWorkingDirectory ( activeDocument , problemFileInfo , domainFileInfo ) { let workingDirectory = "" ; if ( activeDocument . uri . scheme === "file" ) { workingDirectory = dirname ( activeDocument . fileName ) ; } else if ( problemFileInfo . fileUri . scheme === "file" ) { workingDirectory = dirname ( problemFileInfo . fileUri . fsPath ) ; } else if ( domainFileInfo . fileUri . scheme === "file" ) { workingDirectory = dirname ( domainFileInfo . fileUri . fsPath ) ; } return workingDirectory ; } private async parseDomain ( domainFileUri ) { const fileInfo = await this . codePddlWorkspace . upsertAndParseFile ( await workspace . openTextDocument ( domainFileUri ) ) ; if ( fileInfo && ! fileInfo . isDomain ( ) ) { throw new Error ( "Selected file is not a domain file." ) ; } else { return fileInfo as DomainInfo ; } } param domainFileInfo param problemFileInfo param workingDirectory param options async planExplicit ( domainFileInfo , problemFileInfo , workingDirectory , options ? ) { const planParser = new parser . PddlPlannerOutputParser ( domainFileInfo , problemFileInfo , { epsilon : this . pddlConfiguration . getEpsilonTimeStep ( ) } , plans => this . visualizePlans ( plans ) ) ; workingDirectory = await this . adjustWorkingFolder ( workingDirectory ) ; this . planner = await this . createPlanner ( workingDirectory , options ) ; if ( ! this . planner ) { return ; } const planner = this . planner ; if ( planner instanceof Disposable ) { this . context . subscriptions . push ( planner ) ; } this . planningProcessKilled = false ; if ( ! this . isSearchDebugger ( ) ) { this . output . show ( true ) ; } else { commands . executeCommand ( 'pddl.searchDebugger.start' ) ; } window . withProgress < Plan [ ] > ( { location : ProgressLocation . Notification , title : `Searching for plans for domain ${ domainFileInfo . name } and problem ${ problemFileInfo . name }` , cancellable : true , } , ( progress , token ) => { token . onCancellationRequested ( ( ) => { this . planningProcessKilled = true ; this . stopPlanner ( ) ; } ) ; this . progressUpdater = new ElapsedTimeProgressUpdater ( progress , token ) ; return planner . plan ( domainFileInfo , problemFileInfo , planParser , this ) ; } ) . then ( plans => this . onPlannerFinished ( plans ) , reason => this . onPlannerFailed ( reason , planner ) . catch ( showError ) ) ; } isSearchDebugger ( ) { return workspace . getConfiguration ( "pddlPlanner" ) . get ( "executionTarget" ) === "Search debugger" ; } onPlannerFinished ( plans ) { if ( ! this . progressUpdater ) { return ; } const elapsedTime = this . progressUpdater . getElapsedTimeInMilliSecs ( ) ; this . progressUpdater . setFinished ( ) ; const result = this . planningProcessKilled ? PlanningResult . killed ( ) : PlanningResult . success ( plans , elapsedTime ) ; this . _onPlansFound . fire ( result ) ; this . planner = null ; this . output . appendLine ( `Planner found ${ plans . length } plan(s) in ${ this . progressUpdater . getElapsedTimeInMilliSecs ( ) / 1000 }secs.` ) ; this . visualizePlans ( plans ) ; } param reason param failedPlanner async onPlannerFailed ( reason , failedPlanner ) { if ( ! this . progressUpdater ) { return ; } this . progressUpdater . setFinished ( ) ; this . _onPlansFound . fire ( PlanningResult . failure ( reason . toString ( ) ) ) ; this . planner = null ; console . error ( reason ) ; const troubleShootingInfo = await failedPlanner . providerConfiguration . provider ?. troubleshoot ?. ( failedPlanner , reason as unknown ) ; const options = [ { title : failedPlanner . providerConfiguration . configuration . canConfigure ? "Re-configure the planner" : "Show configuration" , action : ( ) : void => { commands . executeCommand ( "pddl.showOverview" ) ; } } ] ; if ( troubleShootingInfo ?. options ) { [ ... troubleShootingInfo . options . keys ( ) ] . forEach ( title => { options . push ( { title : title , action : troubleShootingInfo . options . get ( title ) } ) ; } ) ; } options . push ( { title : "Dismiss" , isCloseAffordance : true } ) ; const message = ( troubleShootingInfo ?. info ?? '' ) + '\n\n\nError: ' + ( reason . message ?? reason ) ; const selection = await window . showErrorMessage < ProcessErrorMessageItem > ( message , ... options ) ; if ( selection ?. action ) { selection . action ( failedPlanner ) ; } } async adjustWorkingFolder ( workingDirectory ) { if ( ! workingDirectory ) { return "" ; } if ( ! await utils . afs . exists ( workingDirectory ) ) { if ( workspace . workspaceFolders ?. length ) { return workspace . workspaceFolders [ 0 ] . uri . fsPath ; } else { return "" ; } } else { return workingDirectory ; } } getPlans ( ) { return this . plans ; } param workingDirectory param options returns async createPlanner ( workingDirectory , options ? ) { const scopedPlannerConfiguration = await this . plannersConfiguration . getOrAskSelectedPlanner ( workspace . getWorkspaceFolder ( Uri . file ( workingDirectory ) ) ) ; if ( ! scopedPlannerConfiguration ) { return null ; } const plannerConfiguration = scopedPlannerConfiguration . configuration ; if ( ! await this . verifyConsentForSendingPddl ( plannerConfiguration ) ) { return null ; } if ( plannerConfiguration . url !== undefined ) { const useAuthentication = this . pddlConfiguration . isPddlPlannerServiceAuthenticationEnabled ( ) ; let authentication = undefined ; if ( useAuthentication ) { const configuration = this . pddlConfiguration . getPddlPlannerServiceAuthenticationConfiguration ( ) ; authentication = new SAuthentication ( configuration . url , configuration . requestEncoded , configuration . clientId , configuration . callbackPort , configuration . timeoutInMs , configuration . tokensvcUrl , configuration . tokensvcApiKey , configuration . tokensvcAccessPath , configuration . tokensvcValidatePath , configuration . tokensvcCodePath , configuration . tokensvcRefreshPath , configuration . tokensvcSvctkPath , configuration . refreshToken , configuration . accessToken , configuration . sToken ) ; } if ( plannerConfiguration . url . endsWith ( "/solve" ) ) { options = await this . getPlannerLineOptions ( plannerConfiguration , options ) ; if ( options === undefined ) { return null ; } const plannerRunConfiguration = { options : options , authentication : authentication } ; return this . codePddlWorkspace . pddlWorkspace . getPlannerRegistrar ( ) . getPlannerProvider ( new planner . PlannerKind ( plannerConfiguration . kind ) ) ?. createPlanner ?. ( plannerConfiguration , plannerRunConfiguration ) ?? SolveServicePlannerProvider . createDefaultPlanner ( plannerConfiguration , plannerRunConfiguration ) ; } else if ( plannerConfiguration . url . endsWith ( "/request" ) ) { const configurationUri = options ? this . toAbsoluteUri ( options , workingDirectory ) : await new PlannerConfigurationSelector ( Uri . file ( workingDirectory ) ) . getConfiguration ( ) ; if ( ! configurationUri ) { return null ; } const plannerRunConfiguration = await PlannerConfigurationSelector . loadConfiguration ( configurationUri , PlannerAsyncService . DEFAULT_TIMEOUT ) as AsyncServiceConfiguration ; plannerRunConfiguration . authentication = authentication ; return this . codePddlWorkspace . pddlWorkspace . getPlannerRegistrar ( ) . getPlannerProvider ( new planner . PlannerKind ( plannerConfiguration . kind ) ) ?. createPlanner ?. ( plannerConfiguration , plannerRunConfiguration ) ?? RequestServicePlannerProvider . createDefaultPlanner ( plannerConfiguration , plannerRunConfiguration ) ; } else { throw new Error ( `Planning service not supported: ${ plannerConfiguration . url }. Only /solve or /request service endpoints are supported.` ) ; } } else if ( plannerConfiguration . path ) { options = await this . getPlannerLineOptions ( plannerConfiguration , options ) ; if ( options === undefined ) { return null ; } const plannerRunConfiguration = { options : options , workingDirectory : workingDirectory , plannerSyntax : plannerConfiguration . syntax } ; const providerConfiguration = { configuration : plannerConfiguration } ; return this . codePddlWorkspace . pddlWorkspace . getPlannerRegistrar ( ) . getPlannerProvider ( new planner . PlannerKind ( plannerConfiguration . kind ) ) ?. createPlanner ?. ( plannerConfiguration , plannerRunConfiguration ) ?? new PlannerExecutable ( plannerConfiguration . path , plannerRunConfiguration , providerConfiguration ) ; } else { throw new Error ( `Planner configuration must define at least one of the properties 'url' or 'path': ${ plannerConfiguration . title }` ) ; } } private toAbsoluteUri ( configPath , workingDirectory ) { const absoluteConfigPath = path . isAbsolute ( configPath ) ? configPath : path . join ( workingDirectory , configPath ) ; return Uri . file ( absoluteConfigPath ) ; } async getPlannerLineOptions ( configuration , options ) { if ( options === undefined ) { const plannerProvider = this . codePddlWorkspace . pddlWorkspace . getPlannerRegistrar ( ) . getPlannerProvider ( new planner . PlannerKind ( configuration . kind ) ) ; return await this . userOptionsProvider . getPlannerOptions ( plannerProvider ) ; } else { return options ; } } PLANNING_SERVICE_CONSENTS = "planningServiceConsents" ; async verifyConsentForSendingPddl ( plannerConfiguration ) { if ( plannerConfiguration . url !== undefined && isHttp ( plannerConfiguration . url ) ) { const consents = this . context . globalState . get < any > ( this . PLANNING_SERVICE_CONSENTS , { } ) ; if ( consents [ plannerConfiguration . url ] ) { return true ; } else { const answer = await window . showWarningMessage ( "Confirm you want to send this PDDL to " + plannerConfiguration . url , { modal : true } , "Yes, send my PDDL to this service." , "No, I do not want to send this PDDL to this service." ) ; const consentGiven = answer !== undefined && answer . toLowerCase ( ) . startsWith ( "yes" ) ; consents [ plannerConfiguration . url ] = consentGiven ; this . context . globalState . update ( this . PLANNING_SERVICE_CONSENTS , consents ) ; return consentGiven ; } } else { return true ; } } stopPlanner ( ) { try { if ( this . planner ) { this . planner . stop ( ) ; this . planner = null ; this . output . appendLine ( 'Process killing requested.' ) ; } } catch ( ex ) { console . error ( ex ) ; } } handleOutput ( outputText ) { this . output . append ( outputText ) ; } handlePlan ( _plan ) { throw new Error ( "Method not implemented." ) ; } visualizePlans ( plans ) { if ( ! this . arePlanListsEqual ( this . plans , plans ) || ! this . planView . hasPlannerOutput ( ) ) { this . plans = [ ... plans ] ; this . planView . setPlannerOutput ( plans , ! this . isSearchDebugger ( ) ) ; } } arePlanListsEqual ( plans1 , plans2 ) { if ( plans1 . length !== plans2 . length ) { return false ; } for ( let i = 0 ; i < plans1 . length ; i ++ ) { if ( ! this . arePlansEqual ( plans1 [ i ] , plans2 [ i ] ) ) { return false ; } } return true ; } arePlansEqual ( plan1 , plan2 ) { if ( plan1 . steps . length !== plan2 . steps . length ) { return false ; } for ( let i = 0 ; i < plan1 . steps . length ; i ++ ) { if ( ! plan1 . steps [ i ] . equals ( plan2 . steps [ i ] , this . pddlConfiguration . getEpsilonTimeStep ( ) ) ) { return false ; } } return true ; } static q ( path ) { return path . includes ( ' ' ) ? `"${ path }"` : path ; } } interface ProcessErrorMessageItem extends MessageItem { title : string ; isCloseAffordance ? : boolean ; action ? : ( planner : planner . Planner ) => void | Promise < void > ; } class ElapsedTimeProgressUpdater { startTime = new Date ( ) ; finished = false ; constructor ( private progress , private token ) { this . reportProgress ( ) ; } getElapsedTimeInMilliSecs ( ) { return new Date ( ) . getTime ( ) - this . startTime . getTime ( ) ; } reportProgress ( ) { if ( this . token . isCancellationRequested || this . finished ) { return ; } setTimeout ( ( ) => { const elapsedTime = new Date ( this . getElapsedTimeInMilliSecs ( ) ) ; this . progress . report ( { message : "Elapsed time: " + elapsedTime . toISOString ( ) . substr ( 11 , 8 ) } ) ; this . reportProgress ( ) ; } , 1000 ) ; } setFinished ( ) { this . finished = true ; } } class ProblemFilePickItem implements QuickPickItem { label : string ; description ? : string ; detail ? : string ; picked ? : boolean ; alwaysShow ? : boolean ; problemInfo : ProblemInfo ; constructor ( problemInfo ) { this . label = PddlWorkspace . getFileInfoName ( problemInfo ) ; this . description = PddlWorkspace . getFolderPath ( problemInfo . fileUri ) ; this . problemInfo = problemInfo ; } }