mirror of
https://fuchsia.googlesource.com/third_party/pigweed.googlesource.com/pigweed/pigweed
synced 2024-09-20 13:51:05 +00:00
pw_web: Better autocomplete and method arguments for RPC methods
Change-Id: I4b96029685631f942386fcb177bbfa90fe2ae0ec Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/108812 Commit-Queue: Asad Memon <asadmemon@google.com> Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
This commit is contained in:
parent
84884eb9b0
commit
20fd552820
|
@ -14,6 +14,7 @@
|
|||
|
||||
import {CompletionContext} from '@codemirror/autocomplete'
|
||||
import {syntaxTree} from '@codemirror/language'
|
||||
import {Device} from "pigweedjs";
|
||||
|
||||
const completePropertyAfter = ['PropertyName', '.', '?.']
|
||||
const dontCompleteIn = [
|
||||
|
@ -23,6 +24,7 @@ const dontCompleteIn = [
|
|||
'VariableDefinition',
|
||||
'PropertyDefinition'
|
||||
]
|
||||
var objectPath = require("object-path");
|
||||
|
||||
export function completeFromGlobalScope(context: CompletionContext) {
|
||||
let nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1)
|
||||
|
@ -41,6 +43,16 @@ export function completeFromGlobalScope(context: CompletionContext) {
|
|||
return completeProperties(from, window[variableName])
|
||||
}
|
||||
}
|
||||
else if (object?.name == 'MemberExpression') {
|
||||
let from = /\./.test(nodeBefore.name) ? nodeBefore.to : nodeBefore.from
|
||||
let variableName = context.state.sliceDoc(object.from, object.to)
|
||||
let variable = resolveWindowVariable(variableName);
|
||||
// @ts-ignore
|
||||
if (typeof variable == 'object') {
|
||||
// @ts-ignore
|
||||
return completeProperties(from, variable, variableName)
|
||||
}
|
||||
}
|
||||
} else if (nodeBefore.name == 'VariableName') {
|
||||
return completeProperties(nodeBefore.from, window)
|
||||
} else if (context.explicit && !dontCompleteIn.includes(nodeBefore.name)) {
|
||||
|
@ -49,14 +61,26 @@ export function completeFromGlobalScope(context: CompletionContext) {
|
|||
return null
|
||||
}
|
||||
|
||||
function completeProperties(from: number, object: Object) {
|
||||
function completeProperties(from: number, object: Object, variableName?: string) {
|
||||
let options = []
|
||||
for (let name in object) {
|
||||
options.push({
|
||||
label: name,
|
||||
// @ts-ignore
|
||||
type: typeof object[name] == 'function' ? 'function' : 'variable'
|
||||
})
|
||||
// @ts-ignore
|
||||
if (object[name] instanceof Function && variableName) {
|
||||
debugger;
|
||||
options.push({
|
||||
label: name,
|
||||
// @ts-ignore
|
||||
detail: getFunctionDetailText(`${variableName}.${name}`),
|
||||
type: 'function'
|
||||
})
|
||||
}
|
||||
else {
|
||||
options.push({
|
||||
label: name,
|
||||
type: 'variable'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
from,
|
||||
|
@ -64,3 +88,20 @@ function completeProperties(from: number, object: Object) {
|
|||
validFor: /^[\w$]*$/
|
||||
}
|
||||
}
|
||||
|
||||
function resolveWindowVariable(variableName: string) {
|
||||
if (objectPath.has(window, variableName)) {
|
||||
return objectPath.get(window, variableName);
|
||||
}
|
||||
}
|
||||
|
||||
function getFunctionDetailText(fullExpression: string): string {
|
||||
if (fullExpression.startsWith("device.rpcs.")) {
|
||||
fullExpression = fullExpression.replace("device.rpcs.", "");
|
||||
}
|
||||
const args = ((window as any).device as Device).getMethodArguments(fullExpression);
|
||||
if (args) {
|
||||
return `(${args.join(", ")})`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"@mui/material": "^5.9.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"next": "12.2.3",
|
||||
"object-path": "^0.11.8",
|
||||
"pigweedjs": "file:../../",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
|
|
|
@ -28,6 +28,7 @@ export class Device {
|
|||
private decoder: Decoder;
|
||||
private encoder: Encoder;
|
||||
private rpcAddress: number;
|
||||
private nameToMethodArgumentsMap: any;
|
||||
client: Client;
|
||||
rpcs: any
|
||||
|
||||
|
@ -40,6 +41,7 @@ export class Device {
|
|||
this.protoCollection = protoCollection;
|
||||
this.decoder = new Decoder();
|
||||
this.encoder = new Encoder();
|
||||
this.nameToMethodArgumentsMap = {};
|
||||
const channels = [
|
||||
new Channel(1, (bytes) => {
|
||||
const hdlcBytes = this.encoder.uiFrame(this.rpcAddress, bytes);
|
||||
|
@ -63,6 +65,10 @@ export class Device {
|
|||
});
|
||||
}
|
||||
|
||||
getMethodArguments(fullPath) {
|
||||
return this.nameToMethodArgumentsMap[fullPath];
|
||||
}
|
||||
|
||||
private setupRpcs() {
|
||||
let rpcMap = {};
|
||||
let channel = this.client.channel();
|
||||
|
@ -101,6 +107,10 @@ export class Device {
|
|||
'return this(arguments);'
|
||||
);
|
||||
|
||||
// We store field names so REPL can show hints in autocomplete using these.
|
||||
this.nameToMethodArgumentsMap[fullMethodPath] = requestFields
|
||||
.map(field => field.getName());
|
||||
|
||||
// We create a new JS function dynamically here that takes
|
||||
// proto message fields as arguments and calls the actual RPC method.
|
||||
let fn = new Function(...functionArguments).bind((args) => {
|
||||
|
|
|
@ -32,6 +32,11 @@ describe('WebSerialTransport', () => {
|
|||
expect(device.rpcs.pw.rpc.EchoService.Echo).toBeDefined();
|
||||
});
|
||||
|
||||
it('has method arguments data', () => {
|
||||
expect(device.getMethodArguments("pw.rpc.EchoService.Echo")).toStrictEqual(["msg"]);
|
||||
expect(device.getMethodArguments("pw.test2.Alpha.Unary")).toStrictEqual(['magic_number']);
|
||||
});
|
||||
|
||||
it('unary rpc sends request to serial', async () => {
|
||||
const helloResponse = new Uint8Array([
|
||||
126, 165, 3, 42, 7, 10, 5, 104,
|
||||
|
|
Loading…
Reference in New Issue
Block a user