diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8cb26..68e2583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how - Rename 'Upgrade firmware' to 'Update firmware'. - Update instrument explorer as new discovered instruments come in rather than after discovery completes - **tsp-toolkit-kic-cli** - Run discovery for lan and visa in parallel +### Added +- TSP language interop feature. +- **tsp-toolkit-language-interop** - Implement tsp language interop feature diff --git a/package-lock.json b/package-lock.json index cb52035..c074b4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.0", + "@tektronix/tsp-language-interop-types": "0.1.0-0", "@types/chai": "4.3.18", "@types/mocha": "10.0.10", "@types/node": "22.10.2", @@ -60,7 +61,10 @@ "@tektronix/script-gen-win32-x64": "0.1.4", "@tektronix/trigger-flow-darwin-arm64": "0.2.1", "@tektronix/trigger-flow-linux-x64": "0.2.1", - "@tektronix/trigger-flow-win32-x64": "0.2.1" + "@tektronix/trigger-flow-win32-x64": "0.2.1", + "@tektronix/tsp-language-interop-darwin-arm64": "0.1.0-0", + "@tektronix/tsp-language-interop-linux-x64": "0.1.0-0", + "@tektronix/tsp-language-interop-win32-x64": "0.1.0-0" } }, "node_modules/@azu/format-text": { @@ -1720,6 +1724,70 @@ "windows-kic-trigger-flow": "bin/kic-trigger-flow.exe" } }, + "node_modules/@tektronix/tsp-language-interop-darwin-arm64": { + "version": "0.1.0-0", + "resolved": "https://npm.pkg.github.com/download/@tektronix/tsp-language-interop-darwin-arm64/0.1.0-0/930e51aba68edd10f281abff8b4337af731b93c7", + "integrity": "sha512-vaTuMOg/VmolNQ6yfKeS2RQUCBj27KN4k8MkElN02FELv0PivzZqb92FwLMeg1JNbnFxmRxvorlm52RnazBKyA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@tektronix/tsp-language-interop-types": "0.1.0-0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tektronix/tsp-language-interop-linux-x64": { + "version": "0.1.0-0", + "resolved": "https://npm.pkg.github.com/download/@tektronix/tsp-language-interop-linux-x64/0.1.0-0/be987ce0f5e81eba4e9d83448f44e8ffbee71c96", + "integrity": "sha512-29c5yuBCUqUTkhX2G6J/gnNpEiyUVmV7hcUqOdFiemQogJ2/erk6nbrE6AiCPYLRMXbPD47CHmA3ut0JK4umFA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "dependencies": { + "@tektronix/tsp-language-interop-types": "0.1.0-0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@tektronix/tsp-language-interop-types": { + "version": "0.1.0-0", + "resolved": "https://npm.pkg.github.com/download/@tektronix/tsp-language-interop-types/0.1.0-0/a467a5dce244790d8aa31b4759041d8a8f278c74", + "integrity": "sha512-ia9dvUdD9rsRFuIDMqZQ93oKQh4F15UmiDjid9ApQGd5Jq8n3nKyvZ/y5Mn+fRXxF3vC9IDVhNMtsLkDMvrE6g==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tektronix/tsp-language-interop-win32-x64": { + "version": "0.1.0-0", + "resolved": "https://npm.pkg.github.com/download/@tektronix/tsp-language-interop-win32-x64/0.1.0-0/ad869063265bf5827d24bcf051592d3816297581", + "integrity": "sha512-6w+EXElPSe5vtk4PNuyCyiPVhrLxV1+oYHRCoSkjzmzo20mYDHgedcWR66rmwAVLUcjWCYS6W5xGn5kbDRAY1w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "dependencies": { + "@tektronix/tsp-language-interop-types": "0.1.0-0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@tektronix/web-help-documents": { "version": "0.19.5", "resolved": "https://npm.pkg.github.com/download/@tektronix/web-help-documents/0.19.5/ebcc346413d2be8a3edceeb6c872f931ed02805d", diff --git a/package.json b/package.json index 90d3914..eca7b22 100644 --- a/package.json +++ b/package.json @@ -335,6 +335,11 @@ "title": "Reset to Defaults", "category": "TSP", "icon": "$(trash)" + }, + { + "command": "tsp.convertToPython", + "title": "Convert TSP to Python", + "category": "TSP" } ], "configuration": { @@ -483,6 +488,11 @@ "when": "activeEditor && ( resourceExtname == .tsp || resourceExtname == .tspa )", "command": "tsp.sendFile", "group": "navigation" + }, + { + "when": "resourceExtname == .tsp", + "command": "tsp.convertToPython", + "group": "navigation" } ], "explorer/context": [ @@ -490,6 +500,11 @@ "when": "activeEditor && ( resourceExtname == .tsp || resourceExtname == .tspa )", "command": "tsp.sendFile", "group": "navigation" + }, + { + "when": "resourceExtname == .tsp", + "command": "tsp.convertToPython", + "group": "navigation" } ], "editor/title/context": [ @@ -763,6 +778,7 @@ "copy-static": "copyfiles -u 1 src/**/*.{js,css} out" }, "devDependencies": { + "@tektronix/tsp-language-interop-types": "0.1.0-0", "@istanbuljs/nyc-config-typescript": "^1.0.0", "@types/chai": "4.3.18", "@types/mocha": "10.0.10", @@ -803,6 +819,9 @@ "xml-js": "1.6.11" }, "optionalDependencies": { + "@tektronix/tsp-language-interop-win32-x64": "0.1.0-0", + "@tektronix/tsp-language-interop-darwin-arm64": "0.1.0-0", + "@tektronix/tsp-language-interop-linux-x64": "0.1.0-0", "@tektronix/kic-cli-darwin-arm64": "0.23.0-1", "@tektronix/kic-cli-linux-x64": "0.23.0-1", "@tektronix/kic-cli-win32-x64": "0.23.0-1", diff --git a/src/extension.ts b/src/extension.ts index d73a400..73bec0f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,6 +24,7 @@ import { CombinedScriptGenDataProvider } from "./combinedScriptGenDataProvider" import { TriggerFlowWebViewManager } from "./triggerFlowWebViewManager" import { GenericSessionStorage } from "./genericSessionStorage" import { isMacOS } from "./utility" +import { convertTspToPython } from "./tspConverter" import { checkSystemDependencies, checkVisaInstallation, @@ -33,6 +34,7 @@ import { } from "./dependencyChecker" let _instrExplorer: InstrumentsExplorer +let _tspConverterDiagnostics: vscode.DiagnosticCollection /** * Represents a contributed TSP Toolkit configuration setting. @@ -299,6 +301,11 @@ export async function activate(context: vscode.ExtensionContext) { const LOGLOC: SourceLocation = { file: "extension.ts", func: "activate()" } Log.info("TSP Toolkit activating", LOGLOC) + // Diagnostic collection for TSP → Python conversion warnings/errors + _tspConverterDiagnostics = + vscode.languages.createDiagnosticCollection("tsp-converter") + context.subscriptions.push(_tspConverterDiagnostics) + // Check for version updates and show announcement Log.debug("Checking for version updates", LOGLOC) void checkVersionAndShowAnnouncement(context) @@ -517,6 +524,12 @@ export async function activate(context: vscode.ExtensionContext) { await resetToolkitDefaults() }, }, + { + name: "tsp.convertToPython", + cb: async (e: vscode.Uri) => { + await convertTspToPython(e, _tspConverterDiagnostics) + }, + }, ]) Log.debug("Setting up HelpDocumentWebView", LOGLOC) diff --git a/src/tspConverter.ts b/src/tspConverter.ts new file mode 100644 index 0000000..abf24c1 --- /dev/null +++ b/src/tspConverter.ts @@ -0,0 +1,128 @@ +import * as path from "path" +import * as vscode from "vscode" +import type { + Diagnostic, + TspInterop, +} from "@tektronix/tsp-language-interop-types" + +function loadTspInterop(): TspInterop { + const packageName = `@tektronix/tsp-language-interop-${process.platform}-${process.arch}` + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const converter = require(packageName) as TspInterop + + return converter +} + +/** + * Read a .tsp file, convert it to Python via the native Rust addon, and open + * the result in a new editor tab. Any converter diagnostics are surfaced in + * the VS Code Problems panel. + */ +export async function convertTspToPython( + uri: vscode.Uri | undefined, + diagnosticCollection: vscode.DiagnosticCollection, +): Promise { + // Allow invocation from command palette (no URI) by falling back to the + // active editor. + const fileUri = + uri ?? + (vscode.window.activeTextEditor?.document.uri.fsPath.endsWith(".tsp") + ? vscode.window.activeTextEditor.document.uri + : undefined) + + if (!fileUri) { + vscode.window.showErrorMessage( + "No TSP file selected. Open a .tsp file or right-click it in the Explorer.", + ) + return + } + + const converter = loadTspInterop() + if (!converter) { + vscode.window.showErrorMessage( + "tsp-converter native addon could not be loaded. " + + "Please ensure the extension was built correctly.", + ) + return + } + + // Read source + let source: string + try { + source = (await vscode.workspace.fs.readFile(fileUri)).toString() + } catch (err) { + vscode.window.showErrorMessage( + `Could not read file: ${err instanceof Error ? err.message : String(err)}`, + ) + return + } + + // Derive a class name from the file name (e.g. "my_script.tsp" → "MyScript") + const baseName = path.basename(fileUri.fsPath, ".tsp") + const className = baseName + .split(/[_\-\s]+/) + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join("") + + // Run converter + let result: { ok: boolean; code?: string; diagnostics: Diagnostic[] } + try { + result = converter.convertTspToPython(source, { + className, + scriptPath: fileUri.fsPath, + }) + } catch (err) { + vscode.window.showErrorMessage( + `Converter error: ${err instanceof Error ? err.message : String(err)}`, + ) + return + } + + // Push diagnostics to the Problems panel + const vsDiagnostics = (result.diagnostics ?? []).map((d: Diagnostic) => { + const range = d.span + ? new vscode.Range( + d.span.startLine - 1, + d.span.startColumn, + d.span.endLine - 1, + d.span.endColumn, + ) + : new vscode.Range(0, 0, 0, 0) + + const severity = + d.severity === "error" + ? vscode.DiagnosticSeverity.Error + : d.severity === "warning" + ? vscode.DiagnosticSeverity.Warning + : vscode.DiagnosticSeverity.Information + + const diag = new vscode.Diagnostic(range, d.message, severity) + diag.code = d.code + if (d.hint) + diag.relatedInformation = [ + new vscode.DiagnosticRelatedInformation( + new vscode.Location(fileUri, range), + d.hint, + ), + ] + return diag + }) + diagnosticCollection.set(fileUri, vsDiagnostics) + + if (!result.ok || !result.code) { + const errMsg = result.diagnostics?.[0]?.message ?? "Unknown error" + vscode.window.showErrorMessage(`TSP conversion failed: ${errMsg}`) + return + } + + // Open generated Python in a new untitled editor tab + const doc = await vscode.workspace.openTextDocument({ + language: "python", + content: result.code, + }) + await vscode.window.showTextDocument(doc, { + viewColumn: vscode.ViewColumn.Beside, + preview: false, + }) +}