From be544f6cc7b961942a78ec9496dac3fdb80c3fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fred=20Lef=C3=A9v=C3=A8re-Laoide?= <90181748+FredLL-Avaiga@users.noreply.github.com> Date: Fri, 20 Jan 2023 18:48:48 +0100 Subject: [PATCH] #5 completion in quick input diagnostics links --- l10n/bundle.l10n.json | 8 +- package.json | 11 +- package.nls.json | 1 + shared/views.ts | 4 + src/context.ts | 5 + src/providers/CompletionItemProvider.ts | 43 +----- src/providers/ConfigDetails.ts | 154 +++++++++++++++++--- src/providers/PythonLinkProvider.ts | 6 +- src/utils/errors.ts | 2 +- src/utils/pythonSymbols.ts | 58 ++++++++ src/utils/utils.ts | 20 +-- webviews/package.json | 2 +- webviews/src/components/DataNodeDetails.tsx | 24 ++- 13 files changed, 240 insertions(+), 98 deletions(-) create mode 100644 src/utils/pythonSymbols.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index a61443a..deb6d0b 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -6,14 +6,16 @@ "Enter a name for a new {0} entity.": "Enter a name for a new {0} entity.", "new {0} name": "new {0} name", "New module name": "New module name", - "Create a new function": "Create a new function", - "Create a new class": "Create a new class", "function name": "function name", "class name": "class name", "No selected element.": "No selected element.", "Select property for {0}.": "Select property for {0}.", "No {0} entity in toml.": "No {0} entity in toml.", "Select {0} entities for {1}.{2}": "Select {0} entities for {1}.{2}", + "Select Python module for {0}.{1}": "Select Python module for {0}.{1}", + "Enter Python module for {0}.{1}": "Enter Python module for {0}.{1}", + "Select Python {0} for {1}.{2}": "Select Python {0} for {1}.{2}", + "Enter Python {0} name for {1}.{2}": "Enter Python {0} name for {1}.{2}", "Select value for {0}.{1}": "Select value for {0}.{1}", "Enter value for {0}.{1}": "Enter value for {0}.{1}", "Select data node": "Select data node", @@ -27,6 +29,8 @@ "Cannot find file for Python {0}: '{1}'.": "Cannot find file for Python {0}: '{1}'.", "Cannot find Python {0}: '{1}'.": "Cannot find Python {0}: '{1}'.", "Main module file has been set up as {0} in Workspace settings": "Main module file has been set up as {0} in Workspace settings", + "Create a new function": "Create a new function", + "Create a new class": "Create a new class", "Select file": "Select file", "edit": "edit", "New Property": "New Property", diff --git a/package.json b/package.json index b8b4f85..8d90abe 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Taipy Studio Configuration Builder", "description": "Visual Studio Code extension for Taipy: Configuration Builder", "publisher": "Taipy", - "version": "0.2.2", + "version": "0.3.0", "homepage": "https://github.com/Avaiga/taipy-studio-config.git", "repository": { "type": "git", @@ -87,6 +87,11 @@ "command": "taipy.config.revealInExplorer", "title": "%taipy.config.commands.taipy.config.revealInExplorer%", "icon": "$(go-to-file)" + }, + { + "command": "taipy.details.showLink", + "title": "%taipy.config.commands.taipy.details.showLink%", + "icon": "$(type-hierarchy)" } ], "customEditors": [ @@ -221,6 +226,10 @@ { "command": "taipy.perspective.showFromDiagram", "when": "webviewId == 'taipy.config.editor.diagram' && webviewSection == 'taipy.node'" + }, + { + "command": "taipy.details.showLink", + "when": "webviewId == 'taipy-config-details' && webviewSection == 'taipy.property'" } ] }, diff --git a/package.nls.json b/package.nls.json index d9412f2..121400c 100644 --- a/package.nls.json +++ b/package.nls.json @@ -6,6 +6,7 @@ "taipy.config.commands.taipy.config.task.create": "Taipy: Create New Task", "taipy.config.commands.taipy.config.pipeline.create": "Taipy: Create New Pipeline", "taipy.config.commands.taipy.config.scenario.create": "Taipy: Create New Scenario", + "taipy.config.commands.taipy.details.showLink": "Taipy: Show Source", "taipy.config.commands.taipy.perspective.show": "Taipy: Show View", "taipy.config.commands.taipy.perspective.showFromDiagram": "Taipy: Show View", "taipy.config.commands.taipy.diagram.addNode": "Taipy: Add/Show node in active View", diff --git a/shared/views.ts b/shared/views.ts index 5ad425c..314760d 100644 --- a/shared/views.ts +++ b/shared/views.ts @@ -11,6 +11,7 @@ * specific language governing permissions and limitations under the License. */ +import { Diagnostic } from "vscode"; import { DisplayModel } from "./diagram"; export const NoDetailsId = "NoDetails"; @@ -23,8 +24,11 @@ export interface DataNodeDetailsProps { nodeType: string; nodeName: string; node: Record; + diagnostics?: Record; } +export type WebDiag = {message?: string; severity?: number; link?: boolean; uri: string}; + export const ConfigEditorId = "ConfigEditor"; export interface ConfigEditorProps { diff --git a/src/context.ts b/src/context.ts index 6c3eca2..d7af406 100644 --- a/src/context.ts +++ b/src/context.ts @@ -93,6 +93,7 @@ export class Context { commands.registerCommand(revealConfigNodeCmd, this.revealConfigNodeInEditors, this); commands.registerCommand("taipy.perspective.show", this.showPerspective, this); commands.registerCommand("taipy.perspective.showFromDiagram", this.showPerspectiveFromDiagram, this); + commands.registerCommand("taipy.details.showLink", this.showPropertyLink, this); // Perspective Provider vsContext.subscriptions.push(workspace.registerTextDocumentContentProvider(PERSPECTIVE_SCHEME, new PerspectiveContentProvider())); // Create Tree Views @@ -299,6 +300,10 @@ export class Context { commands.executeCommand("vscode.openWith", getPerspectiveUri(Uri.parse(item.baseUri, true), item.perspective), ConfigEditorProvider.viewType); } + private showPropertyLink(item: { baseUri: string; }) { + commands.executeCommand("vscode.open", Uri.parse(item.baseUri, true)); + } + getSymbols(uri: string) { return (uri && this.symbolsByUri[uri]) || []; } diff --git a/src/providers/CompletionItemProvider.ts b/src/providers/CompletionItemProvider.ts index 353c602..37be454 100644 --- a/src/providers/CompletionItemProvider.ts +++ b/src/providers/CompletionItemProvider.ts @@ -13,20 +13,16 @@ import { CancellationToken, - commands, CompletionContext, CompletionItem, CompletionItemProvider, CompletionTriggerKind, - DocumentSymbol, l10n, Position, Range, SnippetString, - SymbolKind, TextDocument, TextEdit, - Uri, workspace, } from "vscode"; @@ -37,7 +33,7 @@ import { calculatePythonSymbols, getEnum, getEnumProps, getProperties, isClass, import { TAIPY_STUDIO_SETTINGS_NAME } from "../utils/constants"; import { getDescendantProperties, getPythonSuffix, getSectionName, getSymbol, getSymbolArrayValue, getUnsuffixedName } from "../utils/symbols"; import { getOriginalUri } from "./PerpectiveContentProvider"; -import { getMainPythonUri } from "../utils/utils"; +import { getCreateFunctionOrClassLabel, getModulesAndSymbols } from "../utils/pythonSymbols"; const nodeTypes = [DataNode, Task, Pipeline, Scenario]; const validLinks = nodeTypes.reduce((vl, nt) => { @@ -144,44 +140,13 @@ export class ConfigCompletionItemProvider implements CompletionItemProvider { // get python symbols in repository - const pythonUris = await workspace.findFiles("**/*.py"); - const mainUri = await getMainPythonUri(); - const symbolsByUri = await Promise.all( - pythonUris.map( - (uri) => - new Promise<{ uri: Uri; symbols: DocumentSymbol[] }>((resolve, reject) => { - commands.executeCommand("vscode.executeDocumentSymbolProvider", uri).then((symbols: DocumentSymbol[]) => resolve({ uri, symbols }), reject); - }) - ) - ); - const symbolsWithModule = [] as string[]; - const modulesByUri = pythonUris.reduce((pv, uri) => { - const uriStr = uri.path; - if (uriStr === mainUri?.path) { - pv[uriStr] = "__main__"; - } else { - const paths = workspace.asRelativePath(uri).split("/"); - const file = paths.at(-1); - paths.pop(); - const fileMod = `${file.split(".", 2)[0]}`; - const module = paths.length ? `${paths.join(".")}.${fileMod}` : fileMod; - pv[uriStr] = module; - } - return pv; - }, {} as Record); - symbolsByUri.forEach((su) => { - Array.isArray(su.symbols) && su.symbols.forEach((symbol) => { - if ((isFunction && symbol.kind === SymbolKind.Function) || (!isFunction && symbol.kind === SymbolKind.Class)) { - symbolsWithModule.push(`${modulesByUri[su.uri.path]}.${symbol.name}`); - } - }); - }); - const cis = symbolsWithModule.map((v) => getCompletionItemInString(v, lineText, position, undefined, getPythonSuffix(isFunction))); + const [symbolsWithModule, modulesByUri] = await getModulesAndSymbols(isFunction); const modules = Object.values(modulesByUri); + const cis = symbolsWithModule.map((v) => getCompletionItemInString(v, lineText, position, undefined, getPythonSuffix(isFunction))); modules.push(l10n.t("New module name")); cis.push( getCompletionItemInString( - isFunction ? l10n.t("Create a new function") : l10n.t("Create a new class"), + getCreateFunctionOrClassLabel(isFunction), lineText, position, [modules.length === 1 ? modules[0] : modules, isFunction ? l10n.t("function name") : l10n.t("class name")], diff --git a/src/providers/ConfigDetails.ts b/src/providers/ConfigDetails.ts index 97a53c9..98179bf 100644 --- a/src/providers/ConfigDetails.ts +++ b/src/providers/ConfigDetails.ts @@ -25,20 +25,32 @@ import { WorkspaceEdit, TextDocument, l10n, - SymbolKind, - Position, + languages, + commands, + DocumentLink, + QuickPickItemKind, } from "vscode"; -import { getCspScriptSrc, getDefaultConfig, getNonce, joinPaths } from "../utils/utils"; -import { DataNodeDetailsId, NoDetailsId, webviewsLibraryDir, webviewsLibraryName, containerId, DataNodeDetailsProps, NoDetailsProps } from "../../shared/views"; +import { getCspScriptSrc, getDefaultConfig, getNonce, getPositionFragment, joinPaths } from "../utils/utils"; +import { + DataNodeDetailsId, + NoDetailsId, + webviewsLibraryDir, + webviewsLibraryName, + containerId, + DataNodeDetailsProps, + NoDetailsProps, + WebDiag, +} from "../../shared/views"; import { Action, EditProperty, Refresh } from "../../shared/commands"; import { ViewMessage } from "../../shared/messages"; import { Context } from "../context"; import { getOriginalUri, isUriEqual } from "./PerpectiveContentProvider"; -import { getEnum, getEnumProps, getProperties } from "../schema/validation"; -import { getDescendantProperties, getNodeFromSymbol, getSectionName, getSymbol, getUnsuffixedName } from "../utils/symbols"; +import { getEnum, getEnumProps, getProperties, calculatePythonSymbols, isFunction, isClass } from "../schema/validation"; +import { getDescendantProperties, getNodeFromSymbol, getPythonSuffix, getSectionName, getSymbol, getUnsuffixedName } from "../utils/symbols"; import { getChildType } from "../../shared/childtype"; import { stringify } from "@iarna/toml"; +import { getCreateFunctionOrClassLabel, getModulesAndSymbols } from "../utils/pythonSymbols"; export class ConfigDetailsView implements WebviewViewProvider { private _view: WebviewView; @@ -63,10 +75,41 @@ export class ConfigDetailsView implements WebviewViewProvider { this.configUri = getOriginalUri(uri); this.nodeType = nodeType; this.nodeName = name; - this._view?.webview.postMessage({ - viewId: DataNodeDetailsId, - props: { nodeType, nodeName: name, node } as DataNodeDetailsProps, - } as ViewMessage); + this.getNodeDiagnosticsAndLinks(node).then((diags) => { + this._view?.webview.postMessage({ + viewId: DataNodeDetailsId, + props: { nodeType, nodeName: name, node, diagnostics: Object.keys(diags).length ? diags : undefined } as DataNodeDetailsProps, + } as ViewMessage); + }); + } + + private async getNodeDiagnosticsAndLinks(node: any) { + const diags = languages.getDiagnostics(this.configUri); + const links = (await commands.executeCommand("vscode.executeLinkProvider", this.configUri)) as DocumentLink[]; + if (diags.length || links.length) { + const symbols = this.taipyContext.getSymbols(this.configUri.toString()); + return Object.keys(node).reduce((obj, key) => { + const symbol = getSymbol(symbols, this.nodeType, this.nodeName, key); + if (symbol) { + const diag = diags.find((d) => !!d.range.intersection(symbol.range)); + if (diag) { + obj[key] = { + message: diag.message, + severity: diag.severity, + uri: this.configUri.with({ fragment: getPositionFragment(diag.range.start) }).toString(), + }; + } + const link = links.find((l) => !!l.range.intersection(symbol.range)); + if (link) { + obj[key] = obj[key] || { uri: "" }; + obj[key].uri = link.target?.toString(); + obj[key].link = true; + } + } + return obj; + }, {} as Record); + } + return {}; } //called when a view first becomes visible @@ -132,7 +175,7 @@ export class ConfigDetailsView implements WebviewViewProvider { if (insert) { const nameSymbol = getSymbol(symbols, nodeType, nodeName); propertyRange = nameSymbol.range; - const currentProps = nameSymbol.children.map(s => s.name.toLowerCase()); + const currentProps = nameSymbol.children.map((s) => s.name.toLowerCase()); const properties = (await getProperties(nodeType)).filter((p) => !currentProps.includes(p.toLowerCase())); propertyName = await window.showQuickPick(properties, { canPickMany: false, title: l10n.t("Select property for {0}.", nodeType) }); if (!propertyName) { @@ -147,7 +190,7 @@ export class ConfigDetailsView implements WebviewViewProvider { const childType = getChildType(nodeType); const values = ((propertyValue || []) as string[]).map((v) => getUnsuffixedName(v.toLowerCase())); const childNames = getSymbol(symbols, childType).children.map( - s => ({ label: getSectionName(s.name), picked: values.includes(getUnsuffixedName(s.name.toLowerCase())) } as QuickPickItem) + (s) => ({ label: getSectionName(s.name), picked: values.includes(getUnsuffixedName(s.name.toLowerCase())) } as QuickPickItem) ); if (!childNames.length) { window.showInformationMessage(l10n.t("No {0} entity in toml.", childType)); @@ -162,18 +205,81 @@ export class ConfigDetailsView implements WebviewViewProvider { } newVal = res.map((q) => q.label); } else { - const enumProps = await getEnumProps(); - const enumProp = enumProps.find((p) => p.toLowerCase() === propertyName?.toLowerCase()); - const res = enumProp - ? await window.showQuickPick( - getEnum(enumProp).map((v) => ({ label: v, picked: v === propertyValue })), - { canPickMany: false, title: l10n.t("Select value for {0}.{1}", nodeType, propertyName) } - ) - : await window.showInputBox({ title: l10n.t("Enter value for {0}.{1}", nodeType, propertyName), value: propertyValue as string }); - if (res === undefined) { - return; + await calculatePythonSymbols(); + const isFn = isFunction(propertyName); + if (isFn || isClass(propertyName)) { + const [symbolsWithModule, modulesByUri] = await getModulesAndSymbols(isFn); + const currentModule = propertyValue && (propertyValue as string).split(".", 2)[0]; + let resMod: string; + let resUri: string; + if (Object.keys(modulesByUri).length) { + const items = Object.entries(modulesByUri).map( + ([uri, module]) => ({ label: module, picked: module === currentModule, uri: uri } as QuickPickItem & { uri?: string; create?: boolean }) + ); + items.push({ label: "", kind: QuickPickItemKind.Separator }); + items.push({ label: l10n.t("New module name"), create: true }); + const item = await window.showQuickPick(items, { canPickMany: false, title: l10n.t("Select Python module for {0}.{1}", nodeType, propertyName) }); + if (!item) { + return; + } + if (!item.create) { + resMod = item.label; + resUri = item.uri; + } + } + if (!resMod) { + resMod = await window.showInputBox({ title: l10n.t("Enter Python module for {0}.{1}", nodeType, propertyName), value: currentModule }); + if (resMod) { + resMod = resMod.trim(); + resUri = Object.keys(modulesByUri).find((u) => modulesByUri[u] === resMod); + } + } + if (!resMod) { + return; + } + const symbols = symbolsWithModule.filter((s) => s.split(".", 2)[0] === resMod); + let resFunc: string; + if (symbols.length) { + const currentfunc = propertyValue && propertyValue.includes(".") && `${resMod}.${(propertyValue as string).split(".", 2)[1]}`; + const items = symbols.map((fn) => ({ label: fn, picked: fn === currentfunc } as QuickPickItem & { create?: boolean })); + items.push({ label: "", kind: QuickPickItemKind.Separator }); + items.push({ label: getCreateFunctionOrClassLabel(isFn), create: true }); + const item = await window.showQuickPick(items, { + canPickMany: false, + title: l10n.t("Select Python {0} for {1}.{2}", getPythonSuffix(isFn), nodeType, propertyName), + }); + if (!item) { + return; + } + if (!item.create) { + resFunc = item.label; + } + } + if (!resFunc) { + resFunc = await window.showInputBox({ + title: l10n.t("Enter Python {0} name for {1}.{2}", getPythonSuffix(isFn), nodeType, propertyName), + value: `${resMod}.${getPythonSuffix(isFn)}`, + valueSelection: [resMod.length + 1, resMod.length + 1 + getPythonSuffix(isFn).length], + }); + } + if (!resFunc) { + return; + } + newVal = `${resFunc}:${getPythonSuffix(isFn)}`; + } else { + const enumProps = await getEnumProps(); + const enumProp = enumProps.find((p) => p.toLowerCase() === propertyName?.toLowerCase()); + const res = enumProp + ? await window.showQuickPick( + getEnum(enumProp).map((v) => ({ label: v, picked: v === propertyValue })), + { canPickMany: false, title: l10n.t("Select value for {0}.{1}", nodeType, propertyName) } + ) + : await window.showInputBox({ title: l10n.t("Enter value for {0}.{1}", nodeType, propertyName), value: propertyValue as string }); + if (res === undefined) { + return; + } + newVal = typeof res === "string" ? res : res.label; } - newVal = typeof res === "string" ? res : res.label; } if (insert) { propertyRange = propertyRange.with({ end: propertyRange.end.with({ character: 0 }) }); @@ -185,7 +291,7 @@ export class ConfigDetailsView implements WebviewViewProvider { : TextEdit.replace(propertyRange, stringify.value(newVal).trim()), ]); return workspace.applyEdit(we); -} + } private getHtmlForWebview(webview: Webview) { // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. diff --git a/src/providers/PythonLinkProvider.ts b/src/providers/PythonLinkProvider.ts index 4363b90..8a7e925 100644 --- a/src/providers/PythonLinkProvider.ts +++ b/src/providers/PythonLinkProvider.ts @@ -22,13 +22,13 @@ import { DocumentSymbol, workspace, commands, - Position, } from "vscode"; import { Context } from "../context"; import { getPythonReferences } from "../schema/validation"; +import { getMainPythonUri } from "../utils/pythonSymbols"; import { getUnsuffixedName } from "../utils/symbols"; -import { getMainPythonUri } from "../utils/utils"; +import { getPositionFragment } from "../utils/utils"; export class PythonLinkProvider implements DocumentLinkProvider { static register(vsContext: ExtensionContext, context: Context): void { @@ -108,5 +108,3 @@ export class PythonLinkProvider implements DocumentLinkProvider { return links; } } - -const getPositionFragment = (pos: Position) => `L${pos.line}C${pos.character}`; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index c2a7ea9..fccf5c9 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -19,7 +19,7 @@ import { EXTRACT_STRINGS_RE, getDescendantProperties, getPythonSuffix, getSymbol import { getChildType } from "../../shared/childtype"; import { DataNode, Pipeline, Task } from "../../shared/names"; import { getPythonReferences } from "../schema/validation"; -import { getMainPythonUri } from "./utils"; +import { getMainPythonUri } from "./pythonSymbols"; const diagnoticsCollection = languages.createDiagnosticCollection("taipy-config-symbol"); diff --git a/src/utils/pythonSymbols.ts b/src/utils/pythonSymbols.ts new file mode 100644 index 0000000..863bf02 --- /dev/null +++ b/src/utils/pythonSymbols.ts @@ -0,0 +1,58 @@ +import { commands, DocumentSymbol, l10n, SymbolKind, Uri, window, workspace } from "vscode"; + + +export const getMainPythonUri = async () => { + const workspaceConfig = workspace.getConfiguration("taipyStudio.config", workspace.workspaceFolders[0]); + const mainFile = workspaceConfig.get("mainPythonFile"); + const mainUris = await workspace.findFiles(mainFile, null, 1); + let mainUri = mainUris.length ? mainUris[0] : undefined; + if (!mainUri) { + const pyFiles = await workspace.findFiles("*.py", null, 1); + mainUri = pyFiles.length ? pyFiles[0] : undefined; + if (mainUri) { + workspaceConfig.update("mainPythonFile", workspace.asRelativePath(mainUri)); + window.showInformationMessage(l10n.t("Main module file has been set up as {0} in Workspace settings", workspace.asRelativePath(mainUri))); + } else { + console.warn("No symbol detection as there is no python file in workspace."); + } + } + return mainUri || null; +}; + +export const getCreateFunctionOrClassLabel = (isFunction: boolean) => isFunction ? l10n.t("Create a new function") : l10n.t("Create a new class"); + +export const getModulesAndSymbols = async (isFunction: boolean): Promise<[string[], Record]> => { + const pythonUris = await workspace.findFiles("**/*.py"); + const mainUri = await getMainPythonUri(); + const symbolsByUri = await Promise.all( + pythonUris.map( + (uri) => + new Promise<{ uri: Uri; symbols: DocumentSymbol[] }>((resolve, reject) => { + commands.executeCommand("vscode.executeDocumentSymbolProvider", uri).then((symbols: DocumentSymbol[]) => resolve({ uri, symbols }), reject); + }) + ) + ); + const symbolsWithModule = [] as string[]; + const modulesByUri = pythonUris.reduce((pv, uri) => { + const uriStr = uri.path; + if (uriStr === mainUri?.path) { + pv[uriStr] = "__main__"; + } else { + const paths = workspace.asRelativePath(uri).split("/"); + const file = paths.at(-1); + paths.pop(); + const fileMod = `${file.split(".", 2)[0]}`; + const module = paths.length ? `${paths.join(".")}.${fileMod}` : fileMod; + pv[uriStr] = module; + } + return pv; + }, {} as Record); + symbolsByUri.forEach((su) => { + Array.isArray(su.symbols) && su.symbols.forEach((symbol) => { + if ((isFunction && symbol.kind === SymbolKind.Function) || (!isFunction && symbol.kind === SymbolKind.Class)) { + symbolsWithModule.push(`${modulesByUri[su.uri.path]}.${symbol.name}`); + } + }); + }); + return [symbolsWithModule, modulesByUri]; +}; \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1f18ebb..c94da56 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -import { l10n, Uri, Webview, window, workspace } from "vscode"; +import { l10n, Position, Uri, Webview, window, workspace } from "vscode"; export const getNonce = () => { const crypto = require("crypto"); @@ -34,20 +34,4 @@ export const getDefaultConfig = (webview: Webview, extensionUri: Uri) => { return { icons: {}, l10nUri: bundleName && webview.asWebviewUri(joinPaths(extensionUri, "l10n", bundleName)).toString() }; }; -export const getMainPythonUri = async () => { - const workspaceConfig = workspace.getConfiguration("taipyStudio.config", workspace.workspaceFolders[0]); - const mainFile = workspaceConfig.get("mainPythonFile"); - const mainUris = await workspace.findFiles(mainFile, null, 1); - let mainUri = mainUris.length ? mainUris[0] : undefined; - if (!mainUri) { - const pyFiles = await workspace.findFiles("*.py", null, 1); - mainUri = pyFiles.length ? pyFiles[0] : undefined; - if (mainUri) { - workspaceConfig.update("mainPythonFile", workspace.asRelativePath(mainUri)); - window.showInformationMessage(l10n.t("Main module file has been set up as {0} in Workspace settings", workspace.asRelativePath(mainUri))); - } else { - console.warn("No symbol detection as there is no python file in workspace."); - } - } - return mainUri || null; -}; \ No newline at end of file +export const getPositionFragment = (pos: Position) => `L${pos.line + 1}C${pos.character}`; diff --git a/webviews/package.json b/webviews/package.json index da136cf..e86f1f7 100644 --- a/webviews/package.json +++ b/webviews/package.json @@ -3,7 +3,7 @@ "displayName": "taipy-studio-webviews", "description": "Components for Visual Studio Code extension for Taipy", "publisher": "Avaiga", - "version": "0.2.2", + "version": "0.3.0", "categories": [ "Other" ], diff --git a/webviews/src/components/DataNodeDetails.tsx b/webviews/src/components/DataNodeDetails.tsx index d435c4b..df28b48 100644 --- a/webviews/src/components/DataNodeDetails.tsx +++ b/webviews/src/components/DataNodeDetails.tsx @@ -15,11 +15,18 @@ import { Fragment, MouseEvent, useCallback } from "react"; import * as l10n from "@vscode/l10n"; import { postEditProperty } from "../utils/messaging"; -import { DataNodeDetailsProps } from "../../../shared/views"; +import { DataNodeDetailsProps, WebDiag } from "../../../shared/views"; const getAsString = (val: string | string[]) => (Array.isArray(val) ? (val as string[]).join(", ") : typeof val === "string" ? val : JSON.stringify(val)); -const DataNodePanel = ({ nodeType, nodeName, node }: DataNodeDetailsProps) => { +const getDiagContext = (diag: WebDiag) => JSON.stringify({ + webviewSection: "taipy.property", + baseUri: diag.uri + }); + +const getDiagStyle = (diag: WebDiag) => (diag.severity !== undefined ? {textDecorationLine: "underline", textDecorationStyle: "wavy", textDecorationColor: diag.severity === 0 ? "var(--vscode-editorError-foreground)" : diag.severity === 1 ? "var(--vscode-editorWarning-foreground)" : "var(--vscode-editorInfo-foreground)"} : {textDecorationLine: "underline"}) as React.CSSProperties; + +const DataNodePanel = ({ nodeType, nodeName, node, diagnostics }: DataNodeDetailsProps) => { const editPropertyValue = useCallback((evt: MouseEvent) => { const propertyName = evt.currentTarget.dataset.propertyName; @@ -32,15 +39,16 @@ const DataNodePanel = ({ nodeType, nodeName, node }: DataNodeDetailsProps) => { {nodeType}: {nodeName}
- {Object.entries(node).map(([k, n]) => ( - -
{k}
-
{getAsString(n)}
+ {Object.entries(node).map(([k, n]) => { + const valProps = diagnostics && diagnostics[k] ? {title: diagnostics[k].message, 'data-vscode-context': getDiagContext(diagnostics[k]), style:getDiagStyle(diagnostics[k])} : {}; + return +
{k}
+
{getAsString(n)}
-
- ))} +
; + })}
{l10n.t("New Property")}