import*as Common from'../common/common.js';import*as Formatter from'../formatter/formatter.js';import*as SDK from'../sdk/sdk.js';import*as TextUtils from'../text_utils/text_utils.js';import*as UI from'../ui/ui.js';export class JavaScriptAutocomplete{constructor(){this._expressionCache=new Map();self.SDK.consoleModel.addEventListener(SDK.ConsoleModel.Events.CommandEvaluated,this._clearCache,this);self.UI.context.addFlavorChangeListener(SDK.RuntimeModel.ExecutionContext,this._clearCache,this);self.SDK.targetManager.addModelListener(SDK.DebuggerModel.DebuggerModel,SDK.DebuggerModel.Events.DebuggerResumed,this._clearCache,this);self.SDK.targetManager.addModelListener(SDK.DebuggerModel.DebuggerModel,SDK.DebuggerModel.Events.DebuggerPaused,this._clearCache,this);}
_clearCache(){this._expressionCache.clear();}
async completionsForTextInCurrentContext(fullText,query,force){const trimmedText=fullText.trim();const[mapCompletions,expressionCompletions]=await Promise.all([this._mapCompletions(trimmedText,query),this._completionsForExpression(trimmedText,query,force)]);return mapCompletions.concat(expressionCompletions);}
async argumentsHint(fullText){const functionCall=await Formatter.FormatterWorkerPool.formatterWorkerPool().findLastFunctionCall(fullText);if(!functionCall){return null;}
const executionContext=self.UI.context.flavor(SDK.RuntimeModel.ExecutionContext);if(!executionContext){return null;}
const result=await executionContext.evaluate({expression:functionCall.baseExpression,objectGroup:'argumentsHint',includeCommandLineAPI:true,silent:true,returnByValue:false,generatePreview:false,throwOnSideEffect:functionCall.possibleSideEffects,timeout:functionCall.possibleSideEffects?500:undefined},false,false);if(!result||result.exceptionDetails||!result.object||result.object.type!=='function'){executionContext.runtimeModel.releaseObjectGroup('argumentsHint');return null;}
const args=await this._argumentsForFunction(result.object,async()=>{const result=await executionContext.evaluate({expression:functionCall.receiver,objectGroup:'argumentsHint',includeCommandLineAPI:true,silent:true,returnByValue:false,generatePreview:false,throwOnSideEffect:functionCall.possibleSideEffects,timeout:functionCall.possibleSideEffects?500:undefined},false,false);return(result&&!result.exceptionDetails&&result.object)?result.object:null;},functionCall.functionName);executionContext.runtimeModel.releaseObjectGroup('argumentsHint');if(!args.length||(args.length===1&&!args[0].length)){return null;}
return{args,argumentIndex:functionCall.argumentIndex};}
async _argumentsForFunction(functionObject,receiverObjGetter,parsedFunctionName){const description=functionObject.description;if(!description.endsWith('{ [native code] }')){return[await Formatter.FormatterWorkerPool.formatterWorkerPool().argumentsList(description)];}
if(description==='function () { [native code] }'){const properties=await functionObject.getOwnProperties(false);const internalProperties=properties.internalProperties||[];const targetProperty=internalProperties.find(property=>property.name==='[[TargetFunction]]');const argsProperty=internalProperties.find(property=>property.name==='[[BoundArgs]]');const thisProperty=internalProperties.find(property=>property.name==='[[BoundThis]]');if(thisProperty&&targetProperty&&argsProperty){const originalSignatures=await this._argumentsForFunction(targetProperty.value,()=>Promise.resolve(thisProperty.value));const boundArgsLength=SDK.RemoteObject.RemoteObject.arrayLength(argsProperty.value);const clippedArgs=[];for(const signature of originalSignatures){const restIndex=signature.slice(0,boundArgsLength).findIndex(arg=>arg.startsWith('...'));if(restIndex!==-1){clippedArgs.push(signature.slice(restIndex));}else{clippedArgs.push(signature.slice(boundArgsLength));}}
return clippedArgs;}}
const javaScriptMetadata=await self.runtime.extension(Common.JavaScriptMetaData.JavaScriptMetaData).instance();const name=/^function ([^(]*)\(/.exec(description)[1]||parsedFunctionName;if(!name){return[];}
const uniqueSignatures=javaScriptMetadata.signaturesForNativeFunction(name);if(uniqueSignatures){return uniqueSignatures;}
const receiverObj=await receiverObjGetter();const className=receiverObj.className;if(javaScriptMetadata.signaturesForInstanceMethod(name,className)){return javaScriptMetadata.signaturesForInstanceMethod(name,className);}
if(receiverObj.type==='function'&&receiverObj.description.endsWith('{ [native code] }')){const receiverName=/^function ([^(]*)\(/.exec(receiverObj.description)[1];const staticSignatures=javaScriptMetadata.signaturesForStaticMethod(name,receiverName);if(staticSignatures){return staticSignatures;}}
let protoNames;if(receiverObj.type==='number'){protoNames=['Number','Object'];}else if(receiverObj.type==='string'){protoNames=['String','Object'];}else if(receiverObj.type==='symbol'){protoNames=['Symbol','Object'];}else if(receiverObj.type==='bigint'){protoNames=['BigInt','Object'];}else if(receiverObj.type==='boolean'){protoNames=['Boolean','Object'];}else if(receiverObj.type==='undefined'||receiverObj.subtype==='null'){protoNames=[];}else{protoNames=await receiverObj.callFunctionJSON(function(){const result=[];for(let object=this;object;object=Object.getPrototypeOf(object)){if(typeof object==='object'&&object.constructor&&object.constructor.name){result[result.length]=object.constructor.name;}}
return result;},[]);}
if(!protoNames){return[];}
for(const proto of protoNames){const instanceSignatures=javaScriptMetadata.signaturesForInstanceMethod(name,proto);if(instanceSignatures){return instanceSignatures;}}
return[];}
async _mapCompletions(text,query){const mapMatch=text.match(/\.\s*(get|set|delete)\s*\(\s*$/);const executionContext=self.UI.context.flavor(SDK.RuntimeModel.ExecutionContext);if(!executionContext||!mapMatch){return[];}
const expression=await Formatter.FormatterWorkerPool.formatterWorkerPool().findLastExpression(text.substring(0,mapMatch.index));if(!expression){return[];}
const result=await executionContext.evaluate({expression:expression.baseExpression,objectGroup:'mapCompletion',includeCommandLineAPI:true,silent:true,returnByValue:false,generatePreview:false,throwOnSideEffect:expression.possibleSideEffects,timeout:expression.possibleSideEffects?500:undefined},false,false);if(result.error||!!result.exceptionDetails||result.object.subtype!=='map'){return[];}
const properties=await result.object.getOwnProperties(false);const internalProperties=properties.internalProperties||[];const entriesProperty=internalProperties.find(property=>property.name==='[[Entries]]');if(!entriesProperty){return[];}
const keysObj=await entriesProperty.value.callFunctionJSON(getEntries);executionContext.runtimeModel.releaseObjectGroup('mapCompletion');return gotKeys(Object.keys(keysObj));function getEntries(){const result={__proto__:null};for(let i=0;i<this.length;i++){if(typeof this[i].key==='string'){result[this[i].key]=true;}}
return result;}
function gotKeys(rawKeys){const caseSensitivePrefix=[];const caseInsensitivePrefix=[];const caseSensitiveAnywhere=[];const caseInsensitiveAnywhere=[];let quoteChar='"';if(query.startsWith('\'')){quoteChar='\'';}
let endChar=')';if(mapMatch[0].indexOf('set')!==-1){endChar=', ';}
const sorter=rawKeys.length<1000?String.naturalOrderComparator:undefined;const keys=rawKeys.sort(sorter).map(key=>quoteChar+key+quoteChar);for(const key of keys){if(key.length<query.length){continue;}
if(query.length&&key.toLowerCase().indexOf(query.toLowerCase())===-1){continue;}
const title=key.split('\n').join('\\n');const text=title+endChar;if(key.startsWith(query)){caseSensitivePrefix.push({text:text,title:title,priority:4});}else if(key.toLowerCase().startsWith(query.toLowerCase())){caseInsensitivePrefix.push({text:text,title:title,priority:3});}else if(key.indexOf(query)!==-1){caseSensitiveAnywhere.push({text:text,title:title,priority:2});}else{caseInsensitiveAnywhere.push({text:text,title:title,priority:1});}}
const suggestions=caseSensitivePrefix.concat(caseInsensitivePrefix,caseSensitiveAnywhere,caseInsensitiveAnywhere);if(suggestions.length){suggestions[0].subtitle=Common.UIString.UIString('Keys');}
return suggestions;}}
async _completionsForExpression(fullText,query,force){const executionContext=self.UI.context.flavor(SDK.RuntimeModel.ExecutionContext);if(!executionContext){return[];}
let expression;if(fullText.endsWith('.')||fullText.endsWith('[')){expression=await Formatter.FormatterWorkerPool.formatterWorkerPool().findLastExpression(fullText.substring(0,fullText.length-1));}
if(!expression){if(fullText.endsWith('.')){return[];}
expression={baseExpression:'',possibleSideEffects:false};}
const needsNoSideEffects=expression.possibleSideEffects;const expressionString=expression.baseExpression;const dotNotation=fullText.endsWith('.');const bracketNotation=!!expressionString&&fullText.endsWith('[');if((expressionString&&!isNaN(expressionString))||(!expressionString&&query&&!isNaN(query))){return[];}
if(!query&&!expressionString&&!force){return[];}
const selectedFrame=executionContext.debuggerModel.selectedCallFrame();let completionGroups;const TEN_SECONDS=10000;let cache=this._expressionCache.get(expressionString);if(cache&&cache.date+TEN_SECONDS>Date.now()){completionGroups=await cache.value;}else if(!expressionString&&selectedFrame){cache={date:Date.now(),value:completionsOnPause(selectedFrame)};this._expressionCache.set(expressionString,cache);completionGroups=await cache.value;}else{const resultPromise=executionContext.evaluate({expression:expressionString,objectGroup:'completion',includeCommandLineAPI:true,silent:true,returnByValue:false,generatePreview:false,throwOnSideEffect:needsNoSideEffects,timeout:needsNoSideEffects?500:undefined},false,false);cache={date:Date.now(),value:resultPromise.then(result=>completionsOnGlobal.call(this,result))};this._expressionCache.set(expressionString,cache);completionGroups=await cache.value;}
return this._receivedPropertyNames(completionGroups.slice(0),dotNotation,bracketNotation,expressionString,query);async function completionsOnGlobal(result){if(result.error||!!result.exceptionDetails||!result.object){return[];}
let object=result.object;while(object&&object.type==='object'&&object.subtype==='proxy'){const properties=await object.getOwnProperties(false);const internalProperties=properties.internalProperties||[];const target=internalProperties.find(property=>property.name==='[[Target]]');object=target?target.value:null;}
if(!object){return[];}
let completions=[];if(object.type==='object'||object.type==='function'){completions=await object.callFunctionJSON(getCompletions,[SDK.RemoteObject.RemoteObject.toCallArgument(object.subtype)])||[];}else if(object.type==='string'||object.type==='number'||object.type==='boolean'||object.type==='bigint'){const evaluateResult=await executionContext.evaluate({expression:'('+getCompletions+')("'+object.type+'")',objectGroup:'completion',includeCommandLineAPI:false,silent:true,returnByValue:true,generatePreview:false},false,false);if(evaluateResult.object&&!evaluateResult.exceptionDetails){completions=(evaluateResult.object.value)||[];}}
executionContext.runtimeModel.releaseObjectGroup('completion');if(!expressionString){const globalNames=await executionContext.globalLexicalScopeNames();if(completions.length){completions[0].items=completions[0].items.concat(globalNames);}else{completions.push({items:globalNames.sort(),title:Common.UIString.UIString('Lexical scope variables')});}}
for(const group of completions){for(let i=0;i<group.items.length;i++){group.items[i]=group.items[i].replace(/\n/g,'\\n');}
group.items.sort(group.items.length<1000?this._itemComparator:undefined);}
return completions;function getCompletions(type){let object;if(type==='string'){object=new String('');}else if(type==='number'){object=new Number(0);}
else if(type==='bigint'){object=Object(BigInt(0));}else if(type==='boolean'){object=new Boolean(false);}else{object=this;}
const result=[];try{for(let o=object;o;o=Object.getPrototypeOf(o)){if((type==='array'||type==='typedarray')&&o===object&&o.length>9999){continue;}
const group={items:[],__proto__:null};try{if(typeof o==='object'&&Object.prototype.hasOwnProperty.call(o,'constructor')&&o.constructor&&o.constructor.name){group.title=o.constructor.name;}}catch(ee){}
result[result.length]=group;const names=Object.getOwnPropertyNames(o);const isArray=Array.isArray(o);for(let i=0;i<names.length&&group.items.length<10000;++i){if(isArray&&/^[0-9]/.test(names[i])){continue;}
group.items[group.items.length]=names[i];}}}catch(e){}
return result;}}
async function completionsOnPause(callFrame){const result=[{items:['this']}];const scopeChain=callFrame.scopeChain();const groupPromises=[];for(const scope of scopeChain){groupPromises.push(scope.object().getAllProperties(false,false).then(result=>({properties:result.properties,name:scope.name()})));}
const fullScopes=await Promise.all(groupPromises);executionContext.runtimeModel.releaseObjectGroup('completion');for(const scope of fullScopes){result.push({title:scope.name,items:scope.properties.map(property=>property.name).sort()});}
return result;}}
_receivedPropertyNames(propertyGroups,dotNotation,bracketNotation,expressionString,query){if(!propertyGroups){return[];}
const includeCommandLineAPI=(!dotNotation&&!bracketNotation);if(includeCommandLineAPI){const commandLineAPI=['dir','dirxml','keys','values','profile','profileEnd','monitorEvents','unmonitorEvents','inspect','copy','clear','getEventListeners','debug','undebug','monitor','unmonitor','table','queryObjects','$','$$','$x','$0','$_'];propertyGroups.push({items:commandLineAPI});}
return this._completionsForQuery(dotNotation,bracketNotation,expressionString,query,propertyGroups);}
_completionsForQuery(dotNotation,bracketNotation,expressionString,query,propertyGroups){const quoteUsed=(bracketNotation&&query.startsWith('\''))?'\'':'"';if(!expressionString){const keywords=['await','break','case','catch','class','const','continue','debugger','default','delete','do','else','exports','extends','finally','for','function','if','import','in','instanceof','new','return','super','switch','this','throw','try','typeof','var','void','while','with','yield','let','static','async','of'];propertyGroups.push({title:ls`keywords`,items:keywords.sort()});}
const allProperties=new Set();let result=[];let lastGroupTitle;const regex=/^[a-zA-Z_$\u008F-\uFFFF][a-zA-Z0-9_$\u008F-\uFFFF]*$/;const lowerCaseQuery=query.toLowerCase();for(const group of propertyGroups){const caseSensitivePrefix=[];const caseInsensitivePrefix=[];const caseSensitiveAnywhere=[];const caseInsensitiveAnywhere=[];for(let i=0;i<group.items.length;i++){let property=group.items[i];if(!bracketNotation&&!regex.test(property)){continue;}
if(bracketNotation){if(!/^[0-9]+$/.test(property)){property=quoteUsed+property.escapeCharacters(quoteUsed+'\\')+quoteUsed;}
property+=']';}
if(allProperties.has(property)){continue;}
if(property.length<query.length){continue;}
const lowerCaseProperty=property.toLowerCase();if(query.length&&lowerCaseProperty.indexOf(lowerCaseQuery)===-1){continue;}
allProperties.add(property);if(property.startsWith(query)){caseSensitivePrefix.push({text:property,priority:property===query?5:4});}else if(lowerCaseProperty.startsWith(lowerCaseQuery)){caseInsensitivePrefix.push({text:property,priority:3});}else if(property.indexOf(query)!==-1){caseSensitiveAnywhere.push({text:property,priority:2});}else{caseInsensitiveAnywhere.push({text:property,priority:1});}}
const structuredGroup=caseSensitivePrefix.concat(caseInsensitivePrefix,caseSensitiveAnywhere,caseInsensitiveAnywhere);if(structuredGroup.length&&group.title!==lastGroupTitle){structuredGroup[0].subtitle=group.title;lastGroupTitle=group.title;}
result=result.concat(structuredGroup);result.forEach(item=>{if(item.text.endsWith(']')){item.title=item.text.substring(0,item.text.length-1);}});}
return result;}
_itemComparator(a,b){const aStartsWithUnderscore=a.startsWith('_');const bStartsWithUnderscore=b.startsWith('_');if(aStartsWithUnderscore&&!bStartsWithUnderscore){return 1;}
if(bStartsWithUnderscore&&!aStartsWithUnderscore){return-1;}
return String.naturalOrderComparator(a,b);}
static async isExpressionComplete(expression){const currentExecutionContext=self.UI.context.flavor(SDK.RuntimeModel.ExecutionContext);if(!currentExecutionContext){return true;}
const result=await currentExecutionContext.runtimeModel.compileScript(expression,'',false,currentExecutionContext.id);if(!result.exceptionDetails){return true;}
const description=result.exceptionDetails.exception.description;return!description.startsWith('SyntaxError: Unexpected end of input')&&!description.startsWith('SyntaxError: Unterminated template literal');}}
export class JavaScriptAutocompleteConfig{constructor(editor){this._editor=editor;}
static createConfigForEditor(editor){const autocomplete=new JavaScriptAutocompleteConfig(editor);return{substituteRangeCallback:autocomplete._substituteRange.bind(autocomplete),suggestionsCallback:autocomplete._suggestionsCallback.bind(autocomplete),tooltipCallback:autocomplete._tooltipCallback.bind(autocomplete),};}
_substituteRange(lineNumber,columnNumber){const token=this._editor.tokenAtTextPosition(lineNumber,columnNumber);if(token&&token.type==='js-string'){return new TextUtils.TextRange.TextRange(lineNumber,token.startColumn,lineNumber,columnNumber);}
const lineText=this._editor.line(lineNumber);let index;for(index=columnNumber-1;index>=0;index--){if(' =:[({;,!+-*/&|^<>.\t\r\n'.indexOf(lineText.charAt(index))!==-1){break;}}
return new TextUtils.TextRange.TextRange(lineNumber,index+1,lineNumber,columnNumber);}
async _suggestionsCallback(queryRange,substituteRange,force){const query=this._editor.text(queryRange);const before=this._editor.text(new TextUtils.TextRange.TextRange(0,0,queryRange.startLine,queryRange.startColumn));const token=this._editor.tokenAtTextPosition(substituteRange.startLine,substituteRange.startColumn);if(token){const excludedTokens=new Set(['js-comment','js-string-2','js-def']);const trimmedBefore=before.trim();if(!trimmedBefore.endsWith('[')&&!trimmedBefore.match(/\.\s*(get|set|delete)\s*\(\s*$/)){excludedTokens.add('js-string');}
if(!trimmedBefore.endsWith('.')){excludedTokens.add('js-property');}
if(excludedTokens.has(token.type)){return[];}}
const queryAndAfter=this._editor.line(queryRange.startLine).substring(queryRange.startColumn);const words=await ObjectUI.javaScriptAutocomplete.completionsForTextInCurrentContext(before,query,force);if(!force&&queryAndAfter&&queryAndAfter!==query&&words.some(word=>queryAndAfter.startsWith(word.text)&&query.length!==word.text.length)){return[];}
return words;}
async _tooltipCallback(lineNumber,columnNumber){const before=this._editor.text(new TextUtils.TextRange.TextRange(0,0,lineNumber,columnNumber));const result=await ObjectUI.javaScriptAutocomplete.argumentsHint(before);if(!result){return null;}
const argumentIndex=result.argumentIndex;const tooltip=createElement('div');for(const args of result.args){const argumentsElement=createElement('span');for(let i=0;i<args.length;i++){if(i===argumentIndex||(i<argumentIndex&&args[i].startsWith('...'))){argumentsElement.appendChild(UI.Fragment.html`<b>${args[i]}</b>`);}else{argumentsElement.createTextChild(args[i]);}
if(i<args.length-1){argumentsElement.createTextChild(', ');}}
tooltip.appendChild(UI.Fragment.html`<div class='source-code'>\u0192(${argumentsElement})</div>`);}
return tooltip;}}