Skip to content

Commit

Permalink
add support for external json files (#7)
Browse files Browse the repository at this point in the history
* temp-update

* add support for external json files

* update categories

* crlf

* thanks Fred and Fab
  • Loading branch information
dinhlongviolin1 authored Feb 2, 2023
1 parent 05320a2 commit 5ed9ab9
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 42 deletions.
14 changes: 12 additions & 2 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
{
"Add closing tag": "Add closing tag",
"Create function '{0}'": "Create function '{0}'",
"Replace with '{0}'": "Replace with '{0}'",
"Remove negated value": "Remove negated value",
"Select an element type": "Select an element type",
"Enter element value": "Enter element value",
"Unmatched number of curly braces for expression": "Unmatched number of curly braces for expression",
"Select properties for element '{0}'": "Select properties for element '{0}'",
"Element added": "Element added",
"Visual Element added": "Visual Element added",
"Enter Python executable path. `python` value will be used if the field is empty": "Enter Python executable path. `python` value will be used if the field is empty",
"This path is used to locate the visual element descriptors file": "This path is used to locate the visual element descriptors file",
"Finding visual element descriptors file": "Finding visual element descriptors file",
"Visual element descriptors file was found and updated in workspace settings": "Visual element descriptors file was found and updated in workspace settings",
"Can't find visual element descriptors file with the provided environment": "Can't find visual element descriptors file with the provided environment",
"Missing closing syntax": "Missing closing syntax",
"Missing opening tag": "Missing opening tag",
"Missing matching opening tag identifier '{0}'": "Missing matching opening tag identifier '{0}'",
"Missing matching closing tag identifier '{0}'": "Missing matching closing tag identifier '{0}'",
"Unmatched opening tag identifier '{0}'": "Unmatched opening tag identifier '{0}'",
"Unmatched closing tag identifier '{0}'": "Unmatched closing tag identifier '{0}'",
"Missing closing tag with tag identifier '{0}'": "Missing closing tag with tag identifier '{0}'",
"Missing closing tag for opened tag '{0}'": "Missing closing tag for opened tag '{0}'",
"Missing closing tag": "Missing closing tag",
"Invalid property format": "Invalid property format",
"Invalid property name '{0}'": "Invalid property name '{0}'",
". Do you mean '{0}'?": ". Do you mean '{0}'?",
Expand Down
21 changes: 19 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@
"icon": "assets/taipy-logo.png",
"l10n": "./dist/l10n",
"categories": [
"Linters",
"Data Science",
"Other"
],
"activationEvents": [
"workspaceContains:**/*.md",
"workspaceContains:**/*.py",
"onLanguage:python",
"onLanguage:markdown",
"onCommand:taipy.gui.md.generate"
"onCommand:taipy.gui.md.generate",
"onCommand:taipy.gui.md.findElementFile"
],
"main": "./dist/taipy-studio-gui.js",
"capabilities": {
Expand All @@ -42,6 +45,10 @@
{
"command": "taipy.gui.md.generate",
"title": "%taipy.gui.md.generate.title%"
},
{
"command": "taipy.gui.md.findElementFile",
"title": "%taipy.gui.md.findElementFile.title%"
}
],
"snippets": [
Expand All @@ -53,7 +60,17 @@
"language": "markdown",
"path": "./dist/snippets.json"
}
]
],
"configuration": {
"title": "Taipy Studio Gui Helper",
"properties": {
"taipy.gui.elementsFilePath": {
"type": "string",
"default": "",
"description": "%taipy.gui.elementsFilePath.description%"
}
}
}
},
"scripts": {
"compile": "webpack",
Expand Down
4 changes: 3 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"taipy.gui.md.generate.title": "Generate Taipy GUI element"
"taipy.gui.md.generate.title": "Taipy: Generate visual element",
"taipy.gui.md.findElementFile.title": "Taipy: Locate visual element descriptors file in taipy-gui package",
"taipy.gui.elementsFilePath.description": "Specifies the path for visual element descriptors file. If this property is empty, the default visual element descriptors file will be used"
}
13 changes: 13 additions & 0 deletions src/assets/find_element_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from importlib import util
import inspect
import os

if util.find_spec("taipy") and util.find_spec("taipy.gui"):
from taipy.gui import Gui
element_file_path = f"{os.path.dirname(inspect.getfile(Gui))}{os.sep}webapp{os.sep}viselements.json"
if os.path.exists(element_file_path):
print(f"Path: {element_file_path}")
else:
print("Visual element descriptors file not found in taipy-gui package")
else:
print("taipy-gui python package is not installed")
4 changes: 2 additions & 2 deletions src/gui/codeAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import {
TextDocument,
WorkspaceEdit,
} from "vscode";
import { defaultOnFunctionSignature } from "./constant";

import { DiagnosticCode, PROPERTY_RE } from "./diagnostics";
import { ElementProvider } from "./elementProvider";
import { generateOnFunction, markdownDocumentFilter, pythonDocumentFilter } from "./utils";

export class MarkdownActionProvider implements CodeActionProvider {
Expand Down Expand Up @@ -118,7 +118,7 @@ export class MarkdownActionProvider implements CodeActionProvider {
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)
"\n\n" + generateOnFunction(ElementProvider.getOnFunctionSignature()[onFunctionType] || [["state", "State"]], functionName)
);
return action;
}
Expand Down
60 changes: 54 additions & 6 deletions src/gui/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* specific language governing permissions and limitations under the License.
*/

import { commands, ExtensionContext, l10n, window, workspace, WorkspaceEdit } from "vscode";
import { defaultElementList, defaultElementProperties } from "./constant";
import { countChar, parseProperty } from "./utils";
import { join } from "path";
import { commands, ExtensionContext, l10n, ProgressLocation, window, workspace, WorkspaceEdit } from "vscode";
import { ElementProvider } from "./elementProvider";
import { countChar, execShell, parseProperty, updateFilePath } from "./utils";

interface GuiElement {
elementName: string;
Expand All @@ -33,7 +34,7 @@ export class GenerateGuiCommand {
}

private static async handleGenerateElementCommand() {
const result = await window.showQuickPick(defaultElementList, {
const result = await window.showQuickPick(ElementProvider.getElementList(), {
placeHolder: l10n.t("Select an element type"),
});
if (result === undefined) {
Expand Down Expand Up @@ -62,7 +63,8 @@ export class GenerateGuiCommand {

private static async handleElementPropertySelection(guiElement: GuiElement) {
const quickPick = window.createQuickPick();
const propertyObject = defaultElementProperties[guiElement.elementName as keyof typeof defaultElementProperties];
const elementProperties = ElementProvider.getElementProperties();
const propertyObject = elementProperties[guiElement.elementName as keyof typeof elementProperties];
if (Object.keys(propertyObject).length === 0) {
GenerateGuiCommand.addGuiElement(guiElement);
return;
Expand Down Expand Up @@ -94,6 +96,52 @@ export class GenerateGuiCommand {
}
edit.insert(activeEditor?.document.uri, activeEditor?.selection.active, elementString);
workspace.applyEdit(edit);
window.showInformationMessage(l10n.t("Element added"));
window.showInformationMessage(l10n.t("Visual Element added"));
}
}

export class FindElementsFileCommand {
static register(vsContext: ExtensionContext): void {
new FindElementsFileCommand(vsContext);
}

private constructor(readonly context: ExtensionContext) {
context.subscriptions.push(
commands.registerCommand("taipy.gui.md.findElementFile", FindElementsFileCommand.commandEntry)
);
}

private static async commandEntry() {
const result = await window.showInputBox({
placeHolder: l10n.t("Enter Python executable path. `python` value will be used if the field is empty"),
prompt: l10n.t("This path is used to locate the visual element descriptors file"),
});
if (result === undefined) {
return;
}
window.withProgress(
{
location: ProgressLocation.Notification,
cancellable: false,
title: l10n.t("Finding visual element descriptors file"),
},
async (progress) => {
try {
let execResult = await execShell(
`${result || "python"} ${join(__dirname, "assets", "find_element_file.py")}`
);
if (execResult.startsWith("Path: ")) {
updateFilePath(execResult.substring(6));
window.showInformationMessage(l10n.t("Visual element descriptors file was found and updated in workspace settings"));
} else if (execResult) {
window.showErrorMessage(execResult);
} else {
window.showErrorMessage(l10n.t("Can't find visual element descriptors file with the provided environment"));
}
} catch (error) {
window.showErrorMessage(l10n.t("Can't find visual element descriptors file with the provided environment"));
}
}
);
}
}
22 changes: 14 additions & 8 deletions src/gui/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
TextDocument,
Uri,
} from "vscode";
import { defaultElementList, defaultElementProperties, defaultOnFunctionList, LanguageId } from "./constant";
import { LanguageId } from "./constant";
import { ElementProvider } from "./elementProvider";
import { markdownDocumentFilter, parseProperty, pythonDocumentFilter } from "./utils";

const RE_LINE = /<(([\|]{1})([^\|]*)){1,2}/;
Expand Down Expand Up @@ -83,7 +84,7 @@ export class GuiCompletionItemProvider implements CompletionItemProvider {
return await this.getSymbols(Uri.file(potentialPythonFile), SymbolKind.Variable, CompletionItemKind.Variable);
}
// function name for 'on_*' properties
if (linePrefix.endsWith("=") && defaultOnFunctionList.some((v) => linePrefix.endsWith(v + "="))) {
if (linePrefix.endsWith("=") && ElementProvider.getOnFunctionList().some((v) => linePrefix.endsWith(v + "="))) {
return await this.getSymbols(Uri.file(potentialPythonFile), SymbolKind.Function, CompletionItemKind.Function);
}
}
Expand All @@ -96,26 +97,27 @@ export class GuiCompletionItemProvider implements CompletionItemProvider {
return await this.getSymbols(document.uri, SymbolKind.Variable, CompletionItemKind.Variable);
}
// function name for 'on_*' properties
if (linePrefix.endsWith("=") && defaultOnFunctionList.some((v) => linePrefix.endsWith(v + "="))) {
if (linePrefix.endsWith("=") && ElementProvider.getOnFunctionList().some((v) => linePrefix.endsWith(v + "="))) {
return await this.getSymbols(document.uri, SymbolKind.Function, CompletionItemKind.Function);
}
return this.getCommonCompletion(document, linePrefix);
}

private async getCommonCompletion(document: TextDocument, linePrefix: string): Promise<CompletionItem[]> {
if (linePrefix.endsWith("|")) {
const foundElements = defaultElementList.reduce((p: string[], c: string) => {
const foundElements = ElementProvider.getElementList().reduce((p: string[], c: string) => {
linePrefix.includes(`|${c}`) && p.push(c);
return p;
}, []);
// element type completion
if (linePrefix.match(RE_LINE) && foundElements.length === 0) {
return defaultElementList.map((v) => new CompletionItem(v, CompletionItemKind.Keyword));
return ElementProvider.getElementList().map((v) => new CompletionItem(v, CompletionItemKind.Keyword));
}
// element property completion
if (linePrefix.match(RE_LINE) && foundElements.length > 0) {
const latestElement = foundElements[foundElements.length - 1];
const properties = defaultElementProperties[latestElement as keyof typeof defaultElementProperties];
const elementProperties = ElementProvider.getElementProperties();
const properties = elementProperties[latestElement as keyof typeof elementProperties];
if (properties !== undefined) {
return Object.keys(properties)
.reduce((p: string[], c: string) => {
Expand All @@ -124,7 +126,9 @@ export class GuiCompletionItemProvider implements CompletionItemProvider {
}, [])
.map((v) => {
let completionItem = new CompletionItem(v, CompletionItemKind.Property);
completionItem.documentation = new MarkdownString(parseProperty(properties[v as keyof typeof properties]));
completionItem.documentation = new MarkdownString(
parseProperty(properties[v as keyof typeof properties])
);
return completionItem;
});
}
Expand All @@ -139,6 +143,8 @@ export class GuiCompletionItemProvider implements CompletionItemProvider {
completionItemKind: CompletionItemKind
): Promise<CompletionItem[]> {
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));
return !symbols
? []
: symbols.filter((v) => v.kind === symbolKind).map((v) => new CompletionItem(v.name, completionItemKind));
}
}
6 changes: 4 additions & 2 deletions src/gui/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
* specific language governing permissions and limitations under the License.
*/

import visualElements from "../assets/viselements.json";
import { getBlockElementList, getControlElementList, getElementList, getElementProperties, getOnFunctionList, getOnFunctionSignature } from "./utils";
import { join } from "path";
import { getBlockElementList, getControlElementList, getElementFile, getElementList, getElementProperties, getOnFunctionList, getOnFunctionSignature } from "./utils";

const visualElements = getElementFile(join(__dirname, "assets", "viselements.json")) || {};

// object of all elements each with all of its properties
export const defaultElementProperties = getElementProperties(visualElements);
Expand Down
3 changes: 2 additions & 1 deletion src/gui/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import { ExtensionContext } from "vscode";
import { MarkdownActionProvider } from "./codeAction";
import { GenerateGuiCommand } from "./command";
import { GenerateGuiCommand, FindElementsFileCommand } from "./command";
import { GuiCompletionItemProvider } from "./completion";
import { registerDiagnostics } from "./diagnostics";

Expand All @@ -26,6 +26,7 @@ export class GuiContext {
registerDiagnostics(context);
GuiCompletionItemProvider.register(context);
GenerateGuiCommand.register(context);
FindElementsFileCommand.register(context);
MarkdownActionProvider.register(context);
}
}
11 changes: 6 additions & 5 deletions src/gui/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
workspace,
} from "vscode";
import { findBestMatch } from "string-similarity";
import { defaultElementList, defaultBlockElementList, defaultElementProperties, LanguageId } from "./constant";
import { LanguageId } from "./constant";
import { ElementProvider } from "./elementProvider";

const CONTROL_RE = /<\|(.*?)\|>/;
const OPENING_TAG_RE = /<([0-9a-zA-Z\_\.]*)\|((?:(?!\|>).)*)\s*$/;
Expand Down Expand Up @@ -142,7 +143,7 @@ const getSectionDiagnostics = (diagnosticSection: DiagnosticSection): Diagnostic
element = e;
diagnostics.push(...d);
}
if (defaultBlockElementList.includes(element.type)) {
if (ElementProvider.getBlockElementList().includes(element.type)) {
tagQueue.push([
element,
getRangeOfStringInline(line, openingTagSearch[0], new Position(lineCount, 0)),
Expand Down Expand Up @@ -238,7 +239,7 @@ const getSectionDiagnostics = (diagnosticSection: DiagnosticSection): Diagnostic
if (tagId) {
diagnostics.push(
createWarningDiagnostic(
l10n.t("Missing closing tag with tag identifier '{0}'", tagId),
l10n.t("Missing closing tag for opened tag '{0}'", tagId),
DiagnosticCode.missCTagId,
getRangeFromPosition(p, inlineP)
)
Expand Down Expand Up @@ -266,7 +267,7 @@ const processElement = (
const fragments = s.split(SPLIT_RE).filter((v) => !!v);
const e = buildEmptyTaipyElement();
fragments.forEach((fragment) => {
if (!e.type && defaultElementList.includes(fragment)) {
if (!e.type && ElementProvider.getElementList().includes(fragment)) {
e.type = fragment;
return;
}
Expand All @@ -290,7 +291,7 @@ const processElement = (
const propNameMatch = PROPERTY_NAME_RE.exec(propMatch[2]);
const propName = propNameMatch ? propNameMatch[1] : propMatch[2];
const val = propMatch[3];
const validPropertyList = Object.keys(defaultElementProperties[e.type] || []);
const validPropertyList = Object.keys(ElementProvider.getElementProperties()[e.type] || []);
if (validPropertyList.length !== 0 && !validPropertyList.includes(propName)) {
const bestMatch = findBestMatch(propName, validPropertyList).bestMatch;
let dS = l10n.t("Invalid property name '{0}'", propName);
Expand Down
Loading

0 comments on commit 5ed9ab9

Please sign in to comment.