'use strict' ; import { Uri , ExtensionContext , TextDocument , CodeLens , CancellationToken , CodeLensProvider } from 'vscode' ; import { DomainInfo , ProblemInfo , TimedVariableValue , Variable , ObjectInstance , Parameter , Term , parser , utils , getObjectsInheritingFrom , getTypesInheritingFromPlusSelf , Plan } from 'pddl-workspace' ; import * as path from 'path' ; import { CodePddlWorkspace } from '../workspace/CodePddlWorkspace' ; import { nodeToRange } from '../utils' ; import { DocumentCodeLens , DocumentInsetCodeLens } from './view' ; import { ProblemView , ProblemRenderer , ProblemRendererOptions } from './ProblemView' ; import { CustomViewData , NetworkEdge , NetworkNode , ProblemInitViewData , RelationshipValue , TypeProperties , TypesRelationship } from './model/' ; import { getDomainVisualizationConfigurationDataForPlan } from '../planView/DomainVisualization' ; const asSerializable = utils . serializationUtils . asSerializable ; const makeSerializable = utils . serializationUtils . makeSerializable ; const CONTENT = path . join ( 'views' , 'modelView' , 'static' ) ; const PDDL_PROBLEM_INIT_PREVIEW_COMMAND = "pddl.problem.init.preview" ; const PDDL_PROBLEM_INIT_INSET_COMMAND = "pddl.problem.init.inset" ; const DEFAULT_INSET_HEIGHT = 10 ; export class ProblemInitView extends ProblemView < ProblemInitViewOptions , ProblemInitViewData > implements CodeLensProvider { constructor ( context , codePddlWorkspace ) { super ( context , codePddlWorkspace , new ProblemInitRenderer ( ) , { content : CONTENT , viewCommand : PDDL_PROBLEM_INIT_PREVIEW_COMMAND , insetViewCommand : PDDL_PROBLEM_INIT_INSET_COMMAND , insetHeight : DEFAULT_INSET_HEIGHT , webviewType : 'problemPreview' , webviewHtmlPath : 'problemInitView.html' , allowUnsafeEval : true , webviewOptions : { enableFindWidget : true , retainContextWhenHidden : true , enableScripts : true , localResourceRoots : [ Uri . file ( context . asAbsolutePath ( "views" ) ) ] } } , { displayWidth : 100 } ) ; } async provideCodeLenses ( document , token ) { if ( token . isCancellationRequested ) { return null ; } const problem = await this . parseProblem ( document ) ; if ( token . isCancellationRequested ) { return null ; } if ( ! problem ) { return [ ] ; } const defineNode = problem . syntaxTree . getDefineNodeOrThrow ( ) ; const initNode = defineNode . getFirstChild ( parser . PddlTokenType . OpenBracketOperator , /\s*:init/i ) ; if ( initNode ) { return [ new DocumentCodeLens ( document , nodeToRange ( document , initNode ) ) ] ; } else { return [ ] ; } } async resolveCodeLens ( codeLens , token ) { if ( ! ( codeLens instanceof DocumentCodeLens ) ) { return null ; } if ( token . isCancellationRequested ) { return null ; } const domainAndProblem = await this . getProblemAndDomain ( codeLens . getDocument ( ) ) ; if ( ! domainAndProblem ) { return null ; } if ( token . isCancellationRequested ) { return null ; } if ( codeLens instanceof DocumentInsetCodeLens ) { codeLens . command = { command : PDDL_PROBLEM_INIT_INSET_COMMAND , title : 'View inset' , arguments : [ codeLens . getDocument ( ) . uri , codeLens . getLine ( ) ] } ; return codeLens ; } else { codeLens . command = { command : PDDL_PROBLEM_INIT_PREVIEW_COMMAND , title : 'View' , arguments : [ codeLens . getDocument ( ) . uri ] } ; return codeLens ; } } protected createPreviewPanelTitle ( uri ) { return `:init of '${ path . basename ( uri . fsPath ) }'` ; } } class ProblemInitRenderer implements ProblemRenderer < ProblemInitViewOptions , ProblemInitViewData > { async render ( context , problem , domain , options ) { const renderer = new ProblemInitRendererDelegate ( context , domain , problem , options ) ; return { symmetricRelationshipGraph : { nodes : renderer . getNodes ( ) , relationships : renderer . getRelationships ( ) } , typeProperties : asSerializable ( renderer . getTypeProperties ( ) ) as Map < string , TypeProperties > , typeRelationships : asSerializable ( renderer . getTypeRelationships ( ) ) as TypesRelationship [ ] , scalarValues : asSerializable ( renderer . getScalarValues ( ) ) as Map < string , number | boolean > , customVisualization : ! options . hideCustomViz ? makeSerializable ( await renderer . getCustomVisualization ( ) ) : undefined } ; } } class ProblemInitRendererDelegate { private nodes : Map < string , number > = new Map ( ) ; private relationships : NetworkEdge [ ] = [ ] ; private typeProperties : Map < string , TypeProperties > = new Map ( ) ; private typeRelationships = new Array < TypesRelationship > ( ) ; private scalarValues = new Map < string , boolean | number | string > ( ) ; private serializableDomain : DomainInfo | undefined ; private serializableProblem : ProblemInfo | undefined ; constructor ( _context , private domain , private problem , private options ) { if ( ! options . hide2dGraph ) { this . construct2dGraphData ( ) ; } if ( ! options . hideScalarValues ) { this . constructScalarValues ( ) ; } if ( ! options . hideObjectProperties ) { this . constructObjectProperties ( ) ; } if ( ! options . hideObjectRelationships ) { this . constructObjectRelationships ( ) ; } } private constructScalarValues ( ) { const scalarVariables = this . domain . getPredicates ( ) . concat ( this . domain . getFunctions ( ) ) . filter ( v => v . parameters . length === 0 ) ; const scalarInits = scalarVariables . map ( v => this . toVariableInitialValueTuple ( v ) ) ; scalarInits . forEach ( init => this . addScalar ( init ) ) ; } private toVariableInitialValueTuple ( v ) { return { variable : v , value : this . getInitValue ( v ) } ; } private addScalar ( initValue ) { const variableName = initValue . variable . getFullName ( ) ; if ( ! this . scalarValues . has ( variableName ) ) { this . scalarValues . set ( variableName , initValue . value ?. getValue ( ) !== undefined ? initValue . value ?. getValue ( ) : "undefined" ) ; } } private construct2dGraphData ( ) { const symmetric2dPredicates = this . domain . getPredicates ( ) . filter ( v => this . is2DGraphable ( v ) ) ; const symmetric2dFunctions = this . domain . getFunctions ( ) . filter ( v => this . is2DGraphable ( v ) ) ; const symmetric2dVariables = symmetric2dFunctions . concat ( symmetric2dPredicates ) ; const relatableTypes = utils . Util . distinct ( utils . Util . flatMap ( symmetric2dVariables . map ( v => [ v . parameters [ 0 ] . type , v . parameters [ 1 ] . type ] ) ) ) ; const relatableAndInheritedTypes = utils . Util . distinct ( utils . Util . flatMap ( relatableTypes . map ( type => getTypesInheritingFromPlusSelf ( type , this . domain . getTypeInheritance ( ) ) ) ) ) ; relatableAndInheritedTypes . forEach ( type => this . getObjects ( type ) . forEach ( obj => this . addNode ( obj ) ) ) ; const symmetric2dInits = this . problem . getInits ( ) . filter ( init => init . isSupported ) . filter ( init => symmetric2dVariables . some ( v => v . matchesShortNameCaseInsensitive ( init . getLiftedVariableName ( ) ) ) ) ; symmetric2dInits . forEach ( init => this . addRelationship ( init ) ) ; } private is2DGraphable ( v ) { return ProblemInitRendererDelegate . is2D ( v ) && ( ! this . options . graph2dSymmetricOnly || ProblemInitRendererDelegate . is2DSymmetric ( v ) ) ; } private constructObjectProperties ( ) { this . domain . getTypes ( ) . forEach ( type => this . constructTypeProperties ( type ) ) ; } private constructTypeProperties ( type ) { const typeObjects = this . domain . getConstants ( ) . merge ( this . problem . getObjectsTypeMap ( ) ) . getTypeCaseInsensitive ( type ) ; if ( ! typeObjects ) { return ; } const objects = typeObjects . getObjects ( ) ; if ( objects . length > 0 ) { const typeDetails = this . constructObjectsProperties ( type , objects ) ; if ( typeDetails . propertyNames . length ) { this . typeProperties . set ( type , typeDetails ) ; } } } private constructObjectsProperties ( type , objects ) { const liftedVariables = this . domain . getPredicates ( ) . concat ( this . domain . getFunctions ( ) ) . filter ( variable => this . isTypeProperty ( type , variable ) ) ; const objectsValues = new Map < string , Map < string , boolean | number > > ( ) ; objects . forEach ( objectName => { const objectValues = new Map < string , boolean | number > ( ) ; liftedVariables . forEach ( v => { const value = this . getInitValue ( v . bind ( [ new ObjectInstance ( objectName , type ) ] ) ) ; if ( value ) { objectValues . set ( v . name , value . getValue ( ) ) ; } } ) ; objectsValues . set ( objectName , objectValues ) ; } ) ; return { propertyNames : liftedVariables . map ( v => v . name ) , objects : objectsValues } ; } private getInitValue ( groundedVariable ) { const firstInit = this . problem . getInits ( ) . filter ( init => init . isSupported ) . filter ( viv => viv . getVariableName ( ) . toLowerCase ( ) === groundedVariable . getFullName ( ) . toLowerCase ( ) ) . sort ( viv => viv . getTime ( ) ) . find ( ( ) => true ) ; return firstInit ; } private isTypeProperty ( type , variable ) { const applicableTypes = getTypesInheritingFromPlusSelf ( type , this . domain . getTypeInheritance ( ) ) ; return variable . parameters . length === 1 && applicableTypes . includes ( variable . parameters [ 0 ] . type ) ; } getScalarValues ( ) { return this . scalarValues ; } getTypeProperties ( ) { return this . typeProperties ; } getTypeRelationships ( ) { return this . typeRelationships ; } private constructObjectRelationships ( ) { const binaryRelationships = this . domain . getPredicates ( ) . concat ( this . domain . getFunctions ( ) ) . filter ( v => v . parameters . length === 2 ) ; const relationshipPerTypes = utils . Util . groupBy ( binaryRelationships , r => r . parameters . map ( p => p . type ) . join ( ',' ) ) ; relationshipPerTypes . forEach ( ( relationships , typeNames ) => this . constructTypesRelationships ( typeNames . split ( ',' ) , relationships ) ) ; } private constructTypesRelationships ( types , relationships ) { const typeObjectsMap = new Map < string , string [ ] > ( ) ; types . forEach ( t => typeObjectsMap . set ( t , this . getObjects ( t ) ) ) ; const relationshipsMap = new Map < string , RelationshipValue [ ] > ( ) ; relationships . forEach ( r => relationshipsMap . set ( r . name , this . createTypeRelationships ( r ) ) ) ; this . typeRelationships . push ( { types : typeObjectsMap , relationships : relationshipsMap } ) ; } private createTypeRelationships ( relationship ) { const applicableInits = this . problem . getInits ( ) . filter ( init => relationship . matchesShortNameCaseInsensitive ( init . getLiftedVariableName ( ) ) ) ; return applicableInits . map ( init => this . createRelationshipValue ( init ) ) ; } private createRelationshipValue ( init ) { const parameters = new Map < string , string > ( ) ; const liftedVariable = this . domain . getPredicates ( ) . concat ( this . domain . getFunctions ( ) ) . find ( v => v . name . toLowerCase ( ) === init . getLiftedVariableName ( ) . toLowerCase ( ) ) ; if ( liftedVariable ) { liftedVariable . parameters . forEach ( ( term , index ) => parameters . set ( ( term as Parameter ) . name , init . getVariableName ( ) . split ( ' ' ) [ index + 1 ] ) ) ; } else { init . getVariableName ( ) . split ( ' ' ) . slice ( 1 ) . forEach ( ( term , index ) => parameters . set ( index . toString ( ) , term ) ) ; } return { parameters : parameters , value : init . getValue ( ) } ; } private getObjects ( type ) { return getObjectsInheritingFrom ( this . domain . getConstants ( ) . merge ( this . problem . getObjectsTypeMap ( ) ) , type , this . domain . getTypeInheritance ( ) ) ; } private addNode ( obj ) { if ( ! this . nodes . has ( obj ) ) { this . nodes . set ( obj , this . nodes . size + 1 ) ; } } private addRelationship ( initialValue ) { const edge = this . toEdge ( initialValue ) ; if ( edge ) { this . relationships . push ( edge ) ; } } getNodes ( ) { return [ ... this . nodes . entries ( ) ] . map ( entry => this . toNode ( entry ) ) ; } private toNode ( entry ) { const [ entryLabel , entryId ] = entry ; return { id : entryId , label : entryLabel } ; } private toEdge ( initialValue ) { const variableNameParts = initialValue . getVariableName ( ) . split ( ' ' ) ; const fromName = variableNameParts [ 1 ] ; const toName = variableNameParts [ 2 ] ; let label = variableNameParts [ 0 ] ; if ( variableNameParts . length > 3 ) { label += ' ' + variableNameParts . slice ( 3 ) . join ( ' ' ) ; } if ( ! ( initialValue . getValue ( ) === true ) ) { label += `=${ initialValue . getValue ( ) }` ; } if ( initialValue . getTime ( ) > 0 ) { label += ' @ ' + initialValue . getTime ( ) ; } const fromId = this . nodes . get ( fromName ) ; const toId = this . nodes . get ( toName ) ; if ( fromId !== undefined && toId !== undefined ) { return { from : fromId , to : toId , label : label } ; } else { console . log ( `One or more nodes not found: ${ fromName }, ${ toName }` ) ; return null ; } } getRelationships ( ) { return this . relationships ; } param variable static is2DSymmetric ( variable ) { return ProblemInitRendererDelegate . is2D ( variable ) && variable . parameters [ 0 ] . type === variable . parameters [ 1 ] . type ; } private static is2D ( variable ) { return variable . parameters . length >= 2 ; } private getSerialisableDomain ( ) { return this . serializableDomain ?? ( this . serializableDomain = DomainInfo . clone ( makeSerializable ( this . domain ) ) ) ; } private getSerialisableProblem ( ) { return this . serializableProblem ?? ( this . serializableProblem = ProblemInfo . clone ( makeSerializable ( this . problem ) ) ) ; } async getCustomVisualization ( ) { const plan = new Plan ( [ ] , makeSerializable ( this . getSerialisableDomain ( ) ) , this . getSerialisableProblem ( ) ) ; const domainViz = await getDomainVisualizationConfigurationDataForPlan ( plan ) ; return { plan : plan , state : this . problem . getInits ( ) . filter ( init => init . isSupported ) . map ( init => init . getVariableValue ( ) ) , customVisualizationScript : domainViz . customDomainVisualizationScript , displayWidth : domainViz . width } ; } } interface ProblemInitViewOptions extends ProblemRendererOptions { hideCustomViz ? : boolean ; hideScalarValues ? : boolean ; graph2dSymmetricOnly ? : boolean ; hide2dGraph ? : boolean ; hideObjectProperties ? : boolean ; hideObjectRelationships ? : boolean ; } interface VariableInitialValueTuple { variable : Variable ; value : TimedVariableValue | undefined ; }