import { DebugProtocol } from 'vscode-debugprotocol' ; import { Handles } from 'vscode-debugadapter' ; import { VariableContext } from './chromeDebugAdapter' ; import { Protocol as Crdp } from 'devtools-protocol' ; import * as utils from '../utils' ; import { VariablesManager } from './variablesManager' ; import * as ChromeUtils from './chromeUtils' ; export interface IVariableContainer { expand ( variablesManager , filter ? , start ? , count ? ) : Promise < DebugProtocol . Variable [ ] > ; setValue ( variablesManager , name , value ) : Promise < string > ; } export abstract class BaseVariableContainer implements IVariableContainer { constructor ( protected objectId , protected evaluateName ? ) { } public expand ( variablesManager , filter ? , start ? , count ? ) { return variablesManager . getVariablesForObjectId ( this . objectId , this . evaluateName , filter , start , count ) ; } public setValue ( variablesManager , name , value ) { return utils . errP ( 'setValue not supported by this variable type' ) ; } } export class PropertyContainer extends BaseVariableContainer { public setValue ( variablesManager , name , value ) { return variablesManager . setPropertyValue ( this . objectId , name , value ) ; } } export class LoggedObjects extends BaseVariableContainer { constructor ( private args ) { super ( undefined ) ; } public expand ( variablesManager , filter ? , start ? , count ? ) { return Promise . all ( this . args . map ( ( arg , i ) => variablesManager . remoteObjectToVariable ( '' + i , arg , undefined , false , 'repl' ) ) ) ; } } export class ScopeContainer extends BaseVariableContainer { private _thisObj : Crdp . Runtime . RemoteObject ; private _returnValue : Crdp . Runtime . RemoteObject ; private _frameId : string ; private _origScopeIndex : number ; public constructor ( frameId , origScopeIndex , objectId , thisObj ? , returnValue ? ) { super ( objectId , '' ) ; this . _thisObj = thisObj ; this . _returnValue = returnValue ; this . _frameId = frameId ; this . _origScopeIndex = origScopeIndex ; } /**
     * Call super then insert the 'this' object if needed
     */ public expand ( variablesManager , filter ? , start ? , count ? ) { return super . expand ( variablesManager , 'all' , start , count ) . then ( variables => { if ( this . _thisObj ) { return this . insertRemoteObject ( variablesManager , variables , 'this' , this . _thisObj ) ; } return variables ; } ) . then ( variables => { if ( this . _returnValue ) { return this . insertRemoteObject ( variablesManager , variables , 'Return value' , this . _returnValue ) ; } return variables ; } ) ; } public setValue ( variablesManager , name , value ) { return variablesManager . setVariableValue ( this . _frameId , this . _origScopeIndex , name , value ) ; } private insertRemoteObject ( variablesManager , variables , name , obj ) { return variablesManager . remoteObjectToVariable ( name , obj ) . then ( variable => { variables . unshift ( variable ) ; return variables ; } ) ; } } export class ExceptionContainer extends PropertyContainer { protected _exception : Crdp . Runtime . RemoteObject ; protected constructor ( objectId , exception ) { super ( exception . objectId , undefined ) ; this . _exception = exception ; } /**
     * Expand the exception as if it were a Scope
     */ public static create ( exception ) { return exception . objectId ? new ExceptionContainer ( exception . objectId , exception ) : new ExceptionValueContainer ( exception ) ; } } /**
 * For when a value is thrown instead of an object
 */ export class ExceptionValueContainer extends ExceptionContainer { public constructor ( exception ) { super ( 'EXCEPTION_ID' , exception ) ; } /**
     * Make up a fake 'Exception' property to hold the thrown value, displayed under the Exception Scope
     */ public expand ( variablesManager , filter ? , start ? , count ? ) { const excValuePropDescriptor = < any > { name : 'Exception' , value : this . _exception } ; return variablesManager . propertyDescriptorToVariable ( excValuePropDescriptor ) . then ( variable => [ variable ] ) ; } } export function isIndexedPropName ( name ) { return ! ! name . match ( /^\d+$/ ) ; } const PREVIEW_PROPS_DEFAULT = 3 ; const PREVIEW_PROPS_CONSOLE = 8 ; const PREVIEW_PROP_LENGTH = 50 ; const ELLIPSIS = '…' ; function getArrayPreview ( object , context ? ) { let value = object . description ; if ( object . preview ) { const numProps = context === 'repl' ? PREVIEW_PROPS_CONSOLE : PREVIEW_PROPS_DEFAULT ; const indexedProps = object . preview . properties . filter ( prop => isIndexedPropName ( prop . name ) ) ; const propsWithIdx = indexedProps . slice ( 0 , numProps ) . map ( ( prop , i ) => { return { idx : parseInt ( prop . name , 10 ) , value : propertyPreviewToString ( prop ) } ; } ) ; const propValues = [ ] ; for ( let i = 0 ; i < propsWithIdx . length ; i ++ ) { const prop = propsWithIdx [ i ] ; const prevIdx = i === 0 ? - 1 : propsWithIdx [ i - 1 ] . idx ; if ( prop . idx > prevIdx + 1 ) { propValues . push ( ELLIPSIS ) ; } propValues . push ( prop . value ) ; } let propsPreview = propValues . join ( ', ' ) ; if ( object . preview . overflow || indexedProps . length > numProps ) { propsPreview += ', ' + ELLIPSIS ; } value += ` [${ propsPreview }]` ; } return value ; } function getObjectPreview ( object , context ? ) { let value = object . description ; if ( object . preview ) { const numProps = context === 'repl' ? PREVIEW_PROPS_CONSOLE : PREVIEW_PROPS_DEFAULT ; const props = object . preview . properties . slice ( 0 , numProps ) ; let propsPreview = props . map ( prop => { const name = prop . name || `""` ; return `${ name }: ${ propertyPreviewToString ( prop ) }` ; } ) . join ( ', ' ) ; if ( object . preview . overflow || object . preview . properties . length > numProps ) { propsPreview += ', …' ; } value += ` {${ propsPreview }}` ; } return value ; } function propertyPreviewToString ( prop ) { const value = typeof prop . value === 'undefined' ? `<${ prop . type }>` : trimProperty ( prop . value ) ; return prop . type === 'string' ? `"${ value }"` : value ; } function trimProperty ( value ) { return ( value !== undefined && value !== null && value . length > PREVIEW_PROP_LENGTH ) ? value . substr ( 0 , PREVIEW_PROP_LENGTH ) + ELLIPSIS : value ; } export function getRemoteObjectPreview ( object , stringify = true , context ? ) { if ( object ) { if ( object . type === 'object' ) { return getRemoteObjectPreview_object ( object , context ) ; } else if ( object . type === 'function' ) { return getRemoteObjectPreview_function ( object , context ) ; } else { return getRemoteObjectPreview_primitive ( object , stringify ) ; } } return '' ; } export function getRemoteObjectPreview_object ( object , context ? ) { const objectDescription = object . description || '' ; if ( ( < string > object . subtype ) === 'internal#location' ) { return 'internal#location' ; } else if ( object . subtype === 'null' ) { return 'null' ; } else if ( object . subtype === 'array' || object . subtype === 'typedarray' ) { return getArrayPreview ( object , context ) ; } else if ( object . subtype === 'error' ) { const firstNewlineIdx = objectDescription . indexOf ( '\n' ) ; return firstNewlineIdx >= 0 ? objectDescription . substr ( 0 , firstNewlineIdx ) : objectDescription ; } else if ( object . subtype === 'promise' && object . preview ) { const promiseStatus = object . preview . properties . filter ( prop => prop . name === '[[PromiseStatus]]' ) [ 0 ] ; return promiseStatus ? objectDescription + ' { ' + promiseStatus . value + ' }' : objectDescription ; } else if ( object . subtype === 'generator' && object . preview ) { const generatorStatus = object . preview . properties . filter ( prop => prop . name === '[[GeneratorStatus]]' ) [ 0 ] ; return generatorStatus ? objectDescription + ' { ' + generatorStatus . value + ' }' : objectDescription ; } else if ( object . type === 'object' && object . preview ) { return getObjectPreview ( object , context ) ; } else { return objectDescription ; } } export function getRemoteObjectPreview_primitive ( object , stringify ? ) { if ( typeof object . value === 'undefined' ) { return object . description + '' ; } else if ( object . type === 'number' ) { return object . description || '' + object . value ; } else if ( object . type === 'boolean' ) { return '' + object . value ; } else { return stringify ? `"${ object . value }"` : object . value ; } } export function getRemoteObjectPreview_function ( object , context ? ) { const firstBraceIdx = object . description . indexOf ( '{' ) ; if ( firstBraceIdx >= 0 ) { return object . description . substring ( 0 , firstBraceIdx ) + '{ … }' ; } else { const firstArrowIdx = object . description . indexOf ( '=>' ) ; return firstArrowIdx >= 0 ? object . description . substring ( 0 , firstArrowIdx + 2 ) + ' …' : object . description ; } } export class VariableHandles { private _variableHandles = new Handles < IVariableContainer > ( 1 ) ; private _consoleVariableHandles = new Handles < IVariableContainer > ( 1e5 ) ; public onPaused ( ) { this . _variableHandles . reset ( ) ; } public create ( value , context = 'variables' ) { return this . getHandles ( context ) . create ( value ) ; } public get ( handle ) { return this . _variableHandles . get ( handle ) || this . _consoleVariableHandles . get ( handle ) ; } private getHandles ( context ) { return context === 'repl' ? this . _consoleVariableHandles : this . _variableHandles ; } } export interface IPropCount { indexedVariables : number ; namedVariables : number ; } export function getCollectionNumPropsByPreview ( object ) { let indexedVariables = 0 ; let namedVariables = object . preview . properties . length + 1 ; return { indexedVariables , namedVariables } ; } export function getArrayNumPropsByPreview ( object ) { let indexedVariables = 0 ; const indexedProps = object . preview . properties . filter ( prop => isIndexedPropName ( prop . name ) ) ; if ( indexedProps . length ) { indexedVariables = parseInt ( indexedProps [ indexedProps . length - 1 ] . name , 10 ) + 1 ; } const namedVariables = object . preview . properties . length - indexedProps . length + 2 ; return { indexedVariables , namedVariables } ; } export function createPrimitiveVariableWithValue ( name , value , parentEvaluateName ? ) { return { name , value , variablesReference : 0 , evaluateName : ChromeUtils . getEvaluateName ( parentEvaluateName , name ) } ; } export function createPropertyContainer ( object , evaluateName ) { return new PropertyContainer ( object . objectId , evaluateName ) ; } export function createPrimitiveVariable ( name , object , parentEvaluateName ? , stringify ? ) { const value = getRemoteObjectPreview_primitive ( object , stringify ) ; const variable = createPrimitiveVariableWithValue ( name , value , parentEvaluateName ) ; variable . type = object . type ; return variable ; } export function createFunctionVariable ( name , object , context , handles , parentEvaluateName ? ) { let value ; if ( object . description ) { const firstBraceIdx = object . description . indexOf ( '{' ) ; if ( firstBraceIdx >= 0 ) { value = object . description . substring ( 0 , firstBraceIdx ) + '{ … }' ; } else { const firstArrowIdx = object . description . indexOf ( '=>' ) ; value = firstArrowIdx >= 0 ? object . description . substring ( 0 , firstArrowIdx + 2 ) + ' …' : object . description ; } } else { value = 'function() { … }' ; } const evaluateName = ChromeUtils . getEvaluateName ( parentEvaluateName , name ) ; return < DebugProtocol . Variable > { name , value , type : utils . uppercaseFirstLetter ( object . type ) , variablesReference : handles . create ( new PropertyContainer ( object . objectId , evaluateName ) , context ) , evaluateName } ; }