1// Copyright 2022 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15import { CompletionContext } from '@codemirror/autocomplete'; 16import { syntaxTree } from '@codemirror/language'; 17import { Device } from 'pigweedjs'; 18 19const completePropertyAfter = ['PropertyName', '.', '?.']; 20const dontCompleteIn = [ 21 'TemplateString', 22 'LineComment', 23 'BlockComment', 24 'VariableDefinition', 25 'PropertyDefinition', 26]; 27// eslint-disable-next-line @typescript-eslint/no-var-requires 28const objectPath = require('object-path'); 29 30export function completeFromGlobalScope(context: CompletionContext) { 31 const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1); 32 33 if ( 34 completePropertyAfter.includes(nodeBefore.name) && 35 nodeBefore.parent?.name == 'MemberExpression' 36 ) { 37 const object = nodeBefore.parent.getChild('Expression'); 38 if (object?.name == 'VariableName') { 39 const from = /\./.test(nodeBefore.name) ? nodeBefore.to : nodeBefore.from; 40 const variableName = context.state.sliceDoc(object.from, object.to); 41 // @ts-ignore 42 if (typeof window[variableName] == 'object') { 43 // @ts-ignore 44 return completeProperties(from, window[variableName]); 45 } 46 } else if (object?.name == 'MemberExpression') { 47 const from = /\./.test(nodeBefore.name) ? nodeBefore.to : nodeBefore.from; 48 const variableName = context.state.sliceDoc(object.from, object.to); 49 const variable = resolveWindowVariable(variableName); 50 // @ts-ignore 51 if (typeof variable == 'object') { 52 // @ts-ignore 53 return completeProperties(from, variable, variableName); 54 } 55 } 56 } else if (nodeBefore.name == 'VariableName') { 57 return completeProperties(nodeBefore.from, window); 58 } else if (context.explicit && !dontCompleteIn.includes(nodeBefore.name)) { 59 return completeProperties(context.pos, window); 60 } 61 return null; 62} 63 64function completeProperties( 65 from: number, 66 object: object, 67 variableName?: string, 68) { 69 const options = []; 70 for (const name in object) { 71 // @ts-ignore 72 if (object[name] instanceof Function && variableName) { 73 // eslint-disable-next-line no-debugger 74 debugger; 75 options.push({ 76 label: name, 77 // @ts-ignore 78 detail: getFunctionDetailText(`${variableName}.${name}`), 79 type: 'function', 80 }); 81 } else { 82 options.push({ 83 label: name, 84 type: 'variable', 85 }); 86 } 87 } 88 return { 89 from, 90 options, 91 validFor: /^[\w$]*$/, 92 }; 93} 94 95function resolveWindowVariable(variableName: string) { 96 if (objectPath.has(window, variableName)) { 97 return objectPath.get(window, variableName); 98 } 99} 100 101function getFunctionDetailText(fullExpression: string): string { 102 if (fullExpression.startsWith('device.rpcs.')) { 103 fullExpression = fullExpression.replace('device.rpcs.', ''); 104 } 105 const args = ((window as any).device as Device).getMethodArguments( 106 fullExpression, 107 ); 108 if (args) { 109 return `(${args.join(', ')})`; 110 } 111 return ''; 112} 113