From 9cff665f33cbfc2c9f98345a4b1e2b89957c0dc3 Mon Sep 17 00:00:00 2001 From: dinhlongnguyen Date: Wed, 28 Dec 2022 01:11:00 +0700 Subject: [PATCH] Code Action for unavailable function (#108) * add symbols * add diagnostics * add code action * update with viselements * remove gi * make fred happy * per fred --- gui/l10n/bundle.l10n.json | 3 +- gui/src/assets/viselements.json | 91 +++++++++++++++++++++++++-------- gui/src/gui/codeAction.ts | 46 ++++++++++++++--- gui/src/gui/command.ts | 4 +- gui/src/gui/completion.ts | 11 ++-- gui/src/gui/constant.ts | 4 +- gui/src/gui/diagnostics.ts | 54 +++++++++++++------ gui/src/gui/utils.ts | 50 ++++++++++++------ 8 files changed, 196 insertions(+), 67 deletions(-) diff --git a/gui/l10n/bundle.l10n.json b/gui/l10n/bundle.l10n.json index 016e8ee..65dd368 100644 --- a/gui/l10n/bundle.l10n.json +++ b/gui/l10n/bundle.l10n.json @@ -14,5 +14,6 @@ "Invalid property format": "Invalid property format", "Invalid property name '{0}'": "Invalid property name '{0}'", ". Do you mean '{0}'?": ". Do you mean '{0}'?", - "Negated value of property '{0}' will be ignored": "Negated value of property '{0}' will be ignored" + "Negated value of property '{0}' will be ignored": "Negated value of property '{0}' will be ignored", + "Function '{0}' in property '{1}' is not available": "Function '{0}' in property '{1}' is not available" } \ No newline at end of file diff --git a/gui/src/assets/viselements.json b/gui/src/assets/viselements.json index b70763e..aad6488 100644 --- a/gui/src/assets/viselements.json +++ b/gui/src/assets/viselements.json @@ -4,7 +4,6 @@ "text", { "inherits": [ - "on_change", "shared" ], "properties": [ @@ -47,7 +46,8 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of a function that is triggered when the button is pressed.\nThe parameters of that function are all optional:\n" + "doc": "The name of a function that is triggered when the button is pressed.\nThe parameters of that function are all optional:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] } ] } @@ -293,12 +293,12 @@ { "name": "lon", "type": "indexed(str)", - "doc": "Column name for the longitude value. Cf. [Plotly Map Traces](https://plotly.com/javascript/reference/scattergeo/)." + "doc": "Column name for the longitude value. Cf. [Plotly Map Traces](https://plotly.com/javascript/reference/scattergeo/#scattergeo-lon)." }, { "name": "lat", "type": "indexed(str)", - "doc": "Column name for the latitude value. Cf. [Plotly Map Traces](https://plotly.com/javascript/reference/scattergeo/)." + "doc": "Column name for the latitude value. Cf. [Plotly Map Traces](https://plotly.com/javascript/reference/scattergeo/#scattergeo-lat)." }, { "name": "r", @@ -313,22 +313,37 @@ { "name": "high", "type": "indexed(str)", - "doc": "Column name for the high value. Cf. [Ploty candlestick](https://plotly.com/javascript/reference/candlestick/)." + "doc": "Column name for the high value. Cf. [Ploty Candlestick](https://plotly.com/javascript/reference/candlestick/#candlestick-high)." }, { "name": "low", "type": "indexed(str)", - "doc": "Column name for the low value. Cf. [Ploty candlestick](https://plotly.com/javascript/reference/candlestick/)." + "doc": "Column name for the low value. Cf. [Ploty Candlestick](https://plotly.com/javascript/reference/candlestick/#candlestick-low)." }, { "name": "open", "type": "indexed(str)", - "doc": "Column name for the open value. Cf. [Ploty candlestick](https://plotly.com/javascript/reference/candlestick/)." + "doc": "Column name for the open value. Cf. [Ploty Candlestick](https://plotly.com/javascript/reference/candlestick/#candlestick-open)." }, { "name": "close", "type": "indexed(str)", - "doc": "Column name for the close value. Cf. [Ploty candlestick](https://plotly.com/javascript/reference/candlestick/)." + "doc": "Column name for the close value. Cf. [Ploty Candlestick](https://plotly.com/javascript/reference/candlestick/#candlestick-close)." + }, + { + "name": "locations", + "type": "indexed(str)", + "doc": "Column name for the locations value. Cf. [Ploty Choropleth Map](https://plotly.com/javascript/choropleth-maps/)." + }, + { + "name": "values", + "type": "indexed(str)", + "doc": "Column name for the values value. Cf. [Ploty Pie](https://plotly.com/javascript/reference/pie/#pie-values) or [Ploty FunnelArea](https://plotly.com/javascript/reference/funnelarea/#funnelarea-values)." + }, + { + "name": "labels", + "type": "indexed(str)", + "doc": "Column name for the labels value. Cf. [Ploty Pie](https://plotly.com/javascript/reference/pie/#pie-labels)." }, { "name": "text", @@ -354,7 +369,9 @@ { "name": "on_range_change", "type": "Callback", - "doc": "Callback function called when the visible part of the x axis changes.\nThe function receives four parameters:\n" + "doc": "Callback function called when the visible part of the x axis changes.\nThe function receives four parameters:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] + }, { "name": "columns", @@ -390,7 +407,7 @@ { "name": "marker", "type": "indexed(dict[str, any])", - "doc": "The type of markers used for the indicated trace.\nSee marker for details.\nColor and size can be column name." + "doc": "The type of markers used for the indicated trace.\nSee marker for details.\nColor, opacity, size and symbol can be column name." }, { "name": "line", @@ -448,6 +465,21 @@ "type": "str|int|float", "doc": "The height, in CSS units, of this element." }, + { + "name": "template", + "type": "dict", + "doc": "The plotly layout [template](https://plotly.com/javascript/layout-template/)." + }, + { + "name": "template[dark]", + "type": "dict", + "doc": "The plotly layout template applied over the base template when theme is dark." + }, + { + "name": "template[light]", + "type": "dict", + "doc": "The plotly layout template applied over the base template when theme is not dark." + }, { "name": "decimator", "type": "taipy.gui.data.Decimator", @@ -478,7 +510,8 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of a function that is triggered when the download is initiated.\nAll the parameters of that function are optional:\n" + "doc": "The name of a function that is triggered when the download is initiated.\nAll the parameters of that function are optional:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"]] }, { "name": "auto", @@ -528,7 +561,8 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of the function that will be triggered.\nAll the parameters of that function are optional:\n" + "doc": "The name of the function that will be triggered.\nAll the parameters of that function are optional:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"]] }, { "name": "multiple", @@ -573,7 +607,8 @@ { "name": "on_action", "type": "str", - "doc": "The name of a function that is triggered when the user clicks on the image.\nAll the parameters of that function are optional:\n" + "doc": "The name of a function that is triggered when the user clicks on the image.\nAll the parameters of that function are optional:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "width", @@ -691,7 +726,8 @@ { "name": "on_action", "type": "Callback", - "doc": "The name of the function that will be triggered when a menu option is selected.
\nAll the parameters of that function are optional:\n" + "doc": "The name of the function that will be triggered when a menu option is selected.
\nAll the parameters of that function are optional:\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] } ] } @@ -919,17 +955,26 @@ { "name": "on_edit", "type": "Callback", - "doc": "The name of a function that is to be triggered when a cell edition is validated.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot edit cells." + "doc": "The name of a function that is to be triggered when a cell edition is validated.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot edit cells.", + "signature": [["state", "State"], ["var_name", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "on_delete", "type": "str", - "doc": "The name of a function that is to be triggered when a row is deleted.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot delete rows." + "doc": "The name of a function that is to be triggered when a row is deleted.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot delete rows.", + "signature": [["state", "State"], ["var_name", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "on_add", "type": "str", - "doc": "The name of a function that is to be triggered when the user requests a row to be added.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot add rows." + "doc": "The name of a function that is to be triggered when the user requests a row to be added.
\nAll parameters of that function are optional:\n\nIf this property is not set, the user cannot add rows.", + "signature": [["state", "State"], ["var_name", "str"], ["action", "str"], ["payload", "dict"]] + }, + { + "name": "on_action", + "type": "str", + "doc": "The name of a function that is to be triggered when the user selects a row.
\nAll parameters of that function are optional:\n.", + "signature": [["state", "State"], ["var_name", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "size", @@ -959,7 +1004,8 @@ { "name": "on_action", "type": "Callback", - "doc": "Name of a function that is triggered when a button is pressed.
\nThe parameters of that function are all optional:\n\n" + "doc": "Name of a function that is triggered when a button is pressed.
\nThe parameters of that function are all optional:\n\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "close_label", @@ -1110,7 +1156,8 @@ { "name": "on_close", "type": "Callback", - "doc": "The name of a function that is be triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).\nAll parameters of that function are optional:\n\nIf this property is not set, no function is called when this pane is closed." + "doc": "The name of a function that is be triggered when this pane is closed (if the user clicks outside of it or presses the Esc key).\nAll parameters of that function are optional:\n\nIf this property is not set, no function is called when this pane is closed.", + "signature": [["state", "State"], ["id", "str"], ["action", "str"]] }, { "name": "anchor", @@ -1200,7 +1247,8 @@ { "name": "on_change", "type": "Callback", - "doc": "The name of a function that is triggered when the value is updated.
\nThe parameters of that function are all optional:\n" + "doc": "The name of a function that is triggered when the value is updated.
\nThe parameters of that function are all optional:\n", + "signature": [["state", "State"], ["var_name", "str"], ["value", ""]] } ] } @@ -1252,7 +1300,8 @@ { "name": "on_action", "type": "Callback", - "doc": "Name of a function that is triggered when a specific key is pressed.
\nThe parameters of that function are all optional:\n\n" + "doc": "Name of a function that is triggered when a specific key is pressed.
\nThe parameters of that function are all optional:\n\n", + "signature": [["state", "State"], ["id", "str"], ["action", "str"], ["payload", "dict"]] }, { "name": "action_keys", diff --git a/gui/src/gui/codeAction.ts b/gui/src/gui/codeAction.ts index 6d06afa..2fee2c6 100644 --- a/gui/src/gui/codeAction.ts +++ b/gui/src/gui/codeAction.ts @@ -6,6 +6,7 @@ import { CodeActionProvider, Diagnostic, ExtensionContext, + l10n, languages, Position, Range, @@ -13,14 +14,16 @@ import { TextDocument, WorkspaceEdit, } from "vscode"; +import { defaultOnFunctionSignature } from "./constant"; -import { DiagnosticCode } from "./diagnostics"; -import { markdownDocumentFilter, pythonDocumentFilter } from "./utils"; +import { DiagnosticCode, PROPERTY_RE } from "./diagnostics"; +import { generateOnFunction, markdownDocumentFilter, pythonDocumentFilter } from "./utils"; export class MarkdownActionProvider implements CodeActionProvider { public static readonly providedCodeActionKinds = [CodeActionKind.QuickFix]; - private readonly codeActionMap: Record CodeAction> = { + private readonly codeActionMap: Record CodeAction | null> = { [DiagnosticCode.missCSyntax]: this.createMCSCodeAction, + [DiagnosticCode.functionNotFound]: this.createFNFCodeAction, }; static register(context: ExtensionContext): void { @@ -43,8 +46,8 @@ export class MarkdownActionProvider implements CodeActionProvider { token: CancellationToken ): CodeAction[] { const codeActions: CodeAction[] = []; - context.diagnostics.forEach((v) => { - const codeAction = this.createCodeAction(document, v); + context.diagnostics.forEach((diagnostic) => { + const codeAction = this.createCodeAction(document, diagnostic); codeAction !== null && codeActions.push(codeAction); }); return codeActions; @@ -59,7 +62,7 @@ export class MarkdownActionProvider implements CodeActionProvider { } private createMCSCodeAction(document: TextDocument, diagnostic: Diagnostic): CodeAction { - const action = new CodeAction("Add closing tag", CodeActionKind.QuickFix); + const action = new CodeAction(l10n.t("Add closing tag"), CodeActionKind.QuickFix); action.diagnostics = [diagnostic]; action.isPreferred = true; action.edit = new WorkspaceEdit(); @@ -73,4 +76,35 @@ export class MarkdownActionProvider implements CodeActionProvider { } return action; } + + private createFNFCodeAction(document: TextDocument, diagnostic: Diagnostic): CodeAction | null { + const propMatch = PROPERTY_RE.exec(document.getText(diagnostic.range)); + if (!propMatch) { + return null; + } + const onFunctionType = propMatch[2]; + const functionName = propMatch[3]; + const action = new CodeAction(l10n.t("Create function '{0}'", functionName), CodeActionKind.QuickFix); + const diagnosticPosition = diagnostic.range.end; + const quotePositions: Position[] = document + .getText() + .split(/\r?\n/) + .reduce((obj: Position[], v: string, i: number) => { + return [...obj, ...Array.from(v.matchAll(new RegExp('"""', "g")), (a) => new Position(i, a.index || 0))]; + }, []) + .filter( + (v) => + v.line > diagnosticPosition.line || + (v.line === diagnosticPosition.line && v.character > diagnosticPosition.character) + ); + action.diagnostics = [diagnostic]; + action.isPreferred = true; + action.edit = new WorkspaceEdit(); + action.edit.insert( + document.uri, + quotePositions.length > 0 ? quotePositions[0].translate(0, 3) : new Position(document.lineCount - 1, 0), + "\n\n" + generateOnFunction(defaultOnFunctionSignature[onFunctionType] || [["state", "State"]], functionName) + ); + return action; + } } diff --git a/gui/src/gui/command.ts b/gui/src/gui/command.ts index 960165a..b5216c6 100644 --- a/gui/src/gui/command.ts +++ b/gui/src/gui/command.ts @@ -1,6 +1,6 @@ import { commands, ExtensionContext, l10n, window, workspace, WorkspaceEdit } from "vscode"; import { defaultElementList, defaultElementProperties } from "./constant"; -import { countChar } from "./utils"; +import { countChar, parseProperty } from "./utils"; interface GuiElement { elementName: string; @@ -50,7 +50,7 @@ export class GenerateGuiCommand { } quickPick.items = Object.keys(propertyObject).map((label) => ({ label, - detail: propertyObject[label as keyof typeof propertyObject], + detail: parseProperty(propertyObject[label as keyof typeof propertyObject]), })); quickPick.canSelectMany = true; quickPick.title = l10n.t("Select properties for element '{0}'", guiElement.elementName); diff --git a/gui/src/gui/completion.ts b/gui/src/gui/completion.ts index fef8f6d..45905cf 100644 --- a/gui/src/gui/completion.ts +++ b/gui/src/gui/completion.ts @@ -16,7 +16,7 @@ import { Uri, } from "vscode"; import { defaultElementList, defaultElementProperties, defaultOnFunctionList, LanguageId } from "./constant"; -import { markdownDocumentFilter, pythonDocumentFilter } from "./utils"; +import { markdownDocumentFilter, parseProperty, pythonDocumentFilter } from "./utils"; const RE_LINE = /<(([\|]{1})([^\|]*)){1,2}/; @@ -42,8 +42,7 @@ export class GuiCompletionItemProvider implements CompletionItemProvider { ); } - private constructor(private readonly language: LanguageId) { - } + private constructor(private readonly language: LanguageId) {} public async provideCompletionItems( document: TextDocument, @@ -112,7 +111,7 @@ export class GuiCompletionItemProvider implements CompletionItemProvider { }, []) .map((v) => { let completionItem = new CompletionItem(v, CompletionItemKind.Property); - completionItem.documentation = new MarkdownString(properties[v as keyof typeof properties]); + completionItem.documentation = new MarkdownString(parseProperty(properties[v as keyof typeof properties])); return completionItem; }); } @@ -126,7 +125,7 @@ export class GuiCompletionItemProvider implements CompletionItemProvider { symbolKind: SymbolKind, completionItemKind: CompletionItemKind ): Promise { - let symbols = (await commands.executeCommand("vscode.executeDocumentSymbolProvider", uri)) as SymbolInformation[]; - return symbols.filter((v) => v.kind === symbolKind).map((v) => new CompletionItem(v.name, completionItemKind)); + const symbols = (await commands.executeCommand("vscode.executeDocumentSymbolProvider", uri)) as SymbolInformation[]; + return !symbols ? [] : symbols.filter((v) => v.kind === symbolKind).map((v) => new CompletionItem(v.name, completionItemKind)); } } diff --git a/gui/src/gui/constant.ts b/gui/src/gui/constant.ts index 3debf17..0723a7c 100644 --- a/gui/src/gui/constant.ts +++ b/gui/src/gui/constant.ts @@ -1,5 +1,5 @@ import visualElements from "../assets/viselements.json"; -import { getBlockElementList, getControlElementList, getElementList, getElementProperties, getOnFunctionList } from "./utils"; +import { getBlockElementList, getControlElementList, getElementList, getElementProperties, getOnFunctionList, getOnFunctionSignature } from "./utils"; // object of all elements each with all of its properties export const defaultElementProperties = getElementProperties(visualElements); @@ -13,6 +13,8 @@ export const defaultBlockElementList = getBlockElementList(visualElements); export const defaultOnFunctionList = getOnFunctionList(defaultElementProperties); +export const defaultOnFunctionSignature = getOnFunctionSignature(defaultElementProperties); + export enum LanguageId { py = "python", md = "markdown", diff --git a/gui/src/gui/diagnostics.ts b/gui/src/gui/diagnostics.ts index 0f2b688..ada2d53 100644 --- a/gui/src/gui/diagnostics.ts +++ b/gui/src/gui/diagnostics.ts @@ -1,4 +1,5 @@ import { + commands, Diagnostic, DiagnosticCollection, DiagnosticSeverity, @@ -7,6 +8,8 @@ import { languages, Position, Range, + SymbolInformation, + SymbolKind, TextDocument, window, workspace, @@ -18,12 +21,13 @@ const CONTROL_RE = /<\|(.*?)\|>/; const OPENING_TAG_RE = /<([0-9a-zA-Z\_\.]*)\|((?:(?!\|>).)*)\s*$/; const CLOSING_TAG_RE = /^\s*\|([0-9a-zA-Z\_\.]*)>/; const SPLIT_RE = /(? { +export const registerDiagnostics = async (context: ExtensionContext): Promise => { const mdDiagnosticCollection = languages.createDiagnosticCollection("taipy-gui-markdown"); - const didOpen = workspace.onDidOpenTextDocument((doc) => refreshDiagnostics(doc, mdDiagnosticCollection)); - const didChange = workspace.onDidChangeTextDocument((e) => refreshDiagnostics(e.document, mdDiagnosticCollection)); + const didOpen = workspace.onDidOpenTextDocument(async (doc) => await refreshDiagnostics(doc, mdDiagnosticCollection)); + const didChange = workspace.onDidChangeTextDocument( + async (e) => await refreshDiagnostics(e.document, mdDiagnosticCollection) + ); const didClose = workspace.onDidCloseTextDocument((doc) => mdDiagnosticCollection.delete(doc.uri)); - window.activeTextEditor && refreshDiagnostics(window.activeTextEditor.document, mdDiagnosticCollection); + window.activeTextEditor && (await refreshDiagnostics(window.activeTextEditor.document, mdDiagnosticCollection)); context.subscriptions.push(mdDiagnosticCollection, didOpen, didChange, didClose); }; -const refreshDiagnostics = (doc: TextDocument, diagnosticCollection: DiagnosticCollection) => { +const refreshDiagnostics = async (doc: TextDocument, diagnosticCollection: DiagnosticCollection) => { let diagnostics: Diagnostic[] | undefined = undefined; - const uri = doc.uri.fsPath; - if (uri.endsWith(".md") || doc.languageId === LanguageId.md) { + const uri = doc.uri; + if (uri.path.endsWith(".md") || doc.languageId === LanguageId.md) { diagnostics = getMdDiagnostics(doc); - } else if (uri.endsWith(".py") || doc.languageId === LanguageId.py) { - diagnostics = getPyDiagnostics(doc); + } else if (uri.path.endsWith(".py") || doc.languageId === LanguageId.py) { + diagnostics = await getPyDiagnostics(doc); } - diagnostics && diagnosticCollection.set(doc.uri, diagnostics); + diagnostics && diagnosticCollection.set(uri, diagnostics); }; const getMdDiagnostics = (doc: TextDocument): Diagnostic[] => { return getSectionDiagnostics({ content: doc.getText() }); }; -const getPyDiagnostics = (doc: TextDocument): Diagnostic[] => { +const getPyDiagnostics = async (doc: TextDocument): Promise => { const text = doc.getText(); const d: Diagnostic[] = []; + const symbols = (await commands.executeCommand("vscode.executeDocumentSymbolProvider", doc.uri)) as SymbolInformation[]; const quotePositions: Position[] = text.split(/\r?\n/).reduce((obj: Position[], v: string, i: number) => { - return [...obj, ...[...v.matchAll(new RegExp('"""', "gi"))].map((a) => new Position(i, a.index || 0))]; + return [...obj, ...Array.from(v.matchAll(new RegExp('"""', "g")), (a) => new Position(i, a.index || 0))]; }, []); if (quotePositions.length % 2 !== 0) { return []; @@ -92,6 +100,7 @@ const getPyDiagnostics = (doc: TextDocument): Diagnostic[] => { ...getSectionDiagnostics({ content: getTextFromPositions(text, quotePositions[i], quotePositions[i + 1]), initialPosition: quotePositions[i].translate(0, 3), + symbols: symbols, }) ); } @@ -144,7 +153,8 @@ const getSectionDiagnostics = (diagnosticSection: DiagnosticSection): Diagnostic const [d, _] = processElement( elementMatch[1], new Position(lineCount, line.indexOf(elementMatch[1])), - initialPosition + initialPosition, + diagnosticSection.symbols ); diagnostics.push(...d); } @@ -222,7 +232,12 @@ const getSectionDiagnostics = (diagnosticSection: DiagnosticSection): Diagnostic return diagnostics; }; -const processElement = (s: string, inlinePosition: Position, initialPosition: Position): [Diagnostic[], TaipyElement] => { +const processElement = ( + s: string, + inlinePosition: Position, + initialPosition: Position, + symbols: SymbolInformation[] | undefined = undefined +): [Diagnostic[], TaipyElement] => { const d: Diagnostic[] = []; const fragments = s.split(SPLIT_RE).filter((v) => !!v); const e = buildEmptyTaipyElement(); @@ -274,6 +289,15 @@ const processElement = (s: string, inlinePosition: Position, initialPosition: Po ) ); } + if (propName.startsWith("on_") && symbols && !symbols.some((s) => s.name === val && s.kind === SymbolKind.Function)) { + d.push( + createWarningDiagnostic( + l10n.t("Function '{0}' in property '{1}' is not available", val, propName), + DiagnosticCode.functionNotFound, + getRangeFromPosition(initialPosition, getRangeOfStringInline(s, fragment, inlinePosition)) + ) + ); + } e.properties.push({ name: propName, value: notPrefix ? "False" : val ? val : "True" }); }); e.type = e.type ? e.type : "text"; diff --git a/gui/src/gui/utils.ts b/gui/src/gui/utils.ts index 571ca1e..e056df1 100644 --- a/gui/src/gui/utils.ts +++ b/gui/src/gui/utils.ts @@ -1,5 +1,4 @@ import { DocumentFilter } from "vscode"; -import { LanguageId } from "./constant"; export const countChar = (str: string, char: string): number => { return str.split(char).length - 1; @@ -10,6 +9,7 @@ interface ElementProperty { default_property?: any; type: string; doc: string; + signature?: [string, string][]; } interface ElementDetail { @@ -18,7 +18,7 @@ interface ElementDetail { } // visual elements parser -export const getElementProperties = (visualElements: object): Record> => { +export const getElementProperties = (visualElements: object): Record> => { const blocks: Record = (visualElements["blocks" as keyof typeof visualElements] as any).reduce( (obj: Record, v: any) => { obj[v[0]] = v[1]; @@ -39,8 +39,8 @@ export const getElementProperties = (visualElements: object): Record); - const blocksProperties: Record> = {}; - const controlsProperties: Record> = {}; + const blocksProperties: Record> = {}; + const controlsProperties: Record> = {}; // handle all blocks object Object.keys(blocks).forEach((v: string) => { let elementDetail: ElementDetail = blocks[v]; @@ -65,20 +65,20 @@ export const getElementList = (visualElements: object): string[] => { return [...getControlElementList(visualElements), ...getBlockElementList(visualElements)]; }; -const parseProperty = (property: ElementProperty): string => { +export const parseProperty = (property: ElementProperty): string => { return `[${property.type}]${property.default_property ? "(" + property.default_property.toString() + ")" : ""}: ${ property.doc }`; }; -const parsePropertyList = (propertyList: ElementProperty[] | undefined): Record => { +const parsePropertyList = (propertyList: ElementProperty[] | undefined): Record => { if (!propertyList) { return {}; } - return propertyList.reduce((obj: Record, v: ElementProperty) => { - obj[v.name] = parseProperty(v); + return propertyList.reduce((obj: Record, v: ElementProperty) => { + obj[v.name] = v; return obj; - }, {} as Record); + }, {} as Record); }; const handleElementDetailInherits = ( @@ -86,8 +86,8 @@ const handleElementDetailInherits = ( blocks: Record, controls: Record, undocumented: Record -): Record => { - let properties: Record = {}; +): Record => { + let properties: Record = {}; if (!inherits) { return properties; } @@ -110,14 +110,14 @@ const getElementDetailProperties = ( blocks: Record, controls: Record, undocumented: Record -): Record => { +): Record => { return { ...parsePropertyList(elementDetail.properties), ...handleElementDetailInherits(elementDetail.inherits, blocks, controls, undocumented), }; }; -export const getOnFunctionList = (elementProperties: Record>): string[] => { +export const getOnFunctionList = (elementProperties: Record>): string[] => { const onFunctionList = new Set(); for (const key in elementProperties) { const elementProperty = elementProperties[key]; @@ -130,5 +130,25 @@ export const getOnFunctionList = (elementProperties: Record> +): Record => { + const onFunctionRecord: Record = {}; + for (const key in elementProperties) { + const elementProperty = elementProperties[key]; + for (const k in elementProperty) { + if (k.startsWith("on_")) { + const elements = elementProperty[k]; + onFunctionRecord[k] = elements["signature"] || [["state", "State"]]; + } + } + } + return onFunctionRecord; +}; + +export const generateOnFunction = (signature: [string, string][], functionName: string): string => { + return `def ${functionName}(${signature.map((v) => v[0]).join(", ")}):\n pass\n`; +}; + +export const markdownDocumentFilter: DocumentFilter = { language: "markdown" }; +export const pythonDocumentFilter: DocumentFilter = { language: "python" };