Files
vscode-eda-wave-helper/src/wave_editor.ts

215 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as vscode from 'vscode';
import * as path from 'path';
import { waveMode, lastHoverByDoc, waveMappingEnable, HOVER_FRESH_MS } from './extension';
import { sendAndAwait } from './eda_connect';
import { globalMap } from './signal_mapping';
export let moduleTree: Record<string, { module: string[], last: number }> = {};
export async function requestModuleTree(
sharedDir: string,
topScope: string,
timeoutMs: number
): Promise<Record<string, { module: string[], last: number }>> {
const tree: Record<string, string[]> = {};
const payload = `get_module_tree ${topScope}`;
console.log("Requesting ModuleTree");
vscode.window.showInformationMessage('Requesting ModuleTree');
const res = await sendAndAwait(sharedDir, payload, timeoutMs);
const text = (await res) ?? "";
console.log("Module Hierarchy: ", text)
const rx = /\{([^}]+?)\s+\(([^)]+)\)\}/g; // {<path> (<type>)}
const moduleTree: Record<string, { module: string[], last: number }> = {};
let m: RegExpExecArray | null;
while ((m = rx.exec(text))) {
const rawPath = m[1].trim(); // e.g. "/test/u_queue/u_queue_slot[0]"
const modType = m[2].trim(); // e.g. "queue_slot"
if (rawPath.startsWith("/std") || rawPath.startsWith("/std::")) continue;
console.log("RawPath: ", rawPath);
if (!(rawPath === `/${topScope}` || rawPath.startsWith(`/${topScope}/`))) continue;
console.log("RawPath: ", rawPath);
((moduleTree[modType] ??= { module: [], last: 0 }).module).push(rawPath);
}
for (const entry of Object.values(moduleTree)) {
entry.module.sort((a, b) => a.replace(/[^a-zA-Z0-9]/gi, "").localeCompare( b.replace(/[^a-zA-Z0-9]/gi, ""), undefined, { numeric: true, sensitivity: "base" } ));
for (const elt of entry.module) {
console.log(elt)
}
}
console.log(moduleTree);
return moduleTree;
}
export async function addWave(
mode: String
) {
if (!waveMode) {
await vscode.commands.executeCommand("workbench.action.closeActiveEditor");
return;
}
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const cfg = vscode.workspace.getConfiguration('modelsimWave');
const sharedDir = cfg.get<string>('sharedDir') || '.';
const topScope = cfg.get<string>('topScope') || 'sim:/test/'; // keep in sync with package.json
const timeoutMs = cfg.get<number>('timeoutMs') ?? 5000;
// Prefer last hover over caret
const docKey = editor.document.uri.toString();
const hover = lastHoverByDoc.get(docKey);
const now = Date.now();
const position =
hover && now - hover.at <= HOVER_FRESH_MS ? hover.pos : editor.selection.active;
const wordRange = editor.document.getWordRangeAtPosition(position);
if (!wordRange) {
vscode.window.showInformationMessage('Hover a signal, then press your shortcut.');
return;
}
let tokenText = editor.document.getText(wordRange);
const isVariable = await looksLikeVariable(editor, position);
if (!isVariable) {
const choice = await vscode.window.showWarningMessage(
`"${tokenText}" doesnt look like a variable here. Add anyway?`,
'Add',
'Cancel'
);
if (choice !== 'Add') return;
}
if (waveMappingEnable) {
if (globalMap.has(tokenText)) {
tokenText = globalMap.get(tokenText)!; // "!" asserts it's not undefined
// do something with truc
}
}
// Load module tree once per session
if (Object.keys(moduleTree).length === 0) {
moduleTree = await requestModuleTree(sharedDir, topScope, timeoutMs);
console.log("Lenght: ", Object.keys(moduleTree).length);
if (Object.keys(moduleTree).length === 0) {
vscode.window.showErrorMessage('Failed to load module hierarchy from ModelSim.');
return;
}
}
// Get module name from file name (language-independent)
const modName = path.basename(editor.document.fileName, path.extname(editor.document.fileName));
let instancePaths =
moduleTree[modName].module ?? moduleTree[modName.toLowerCase()].module;
if (!instancePaths || instancePaths.length === 0) {
console.log(
`No instance paths found for module "${modName}". ` +
`Ensure your poller uses "find instances" and the design is loaded.`
);
return;
}
// Add the token for every instance of this module (dedupe paths just in case)
instancePaths = Array.from(new Set(instancePaths)).map(p => p.replace(/\/+/g, '/'));
let instance_num = moduleTree[modName].last ?? moduleTree[modName.toLowerCase()].last;
if (instancePaths.length > 1 && mode === 'set') {
const input_num = await vscode.window.showInputBox({
title: 'Enter a number',
prompt: 'This number will be used by the action.',
placeHolder: moduleTree[modName].last !== undefined ? `Press Enter to reuse ${moduleTree[modName].last}` : 'e.g. 42',
value: moduleTree[modName].last !== undefined ? String(moduleTree[modName].last) : '',
ignoreFocusOut: true,
validateInput: (value) => {
if (value.trim() === '') return null; // allow Enter to reuse last
const n = Number(value);
if (!Number.isInteger(n)) return 'Please enter an integer.';
if (n < 0) return 'Please enter a non-negative integer.';
// optionally cap it
if (n > 10000) return 'Thats too large (max 10000).';
return null;
},
});
if (input_num === undefined) {
// User hit Esc — do nothing
return;
}
// Empty input means reuse last (if any)
instance_num = (input_num.trim() === '' || Number(input_num) >= moduleTree[modName].module.length) && moduleTree[modName].last !== undefined ? moduleTree[modName].last : Number(input_num);
moduleTree[modName].last = instance_num;
}
let ok = true;
// for (const p of instancePaths) {
const cmd = `quietly add wave -noupdate \{${instancePaths[instance_num]}/${tokenText}\}`.replace(/\/+/g, '/');
console.log(cmd);
const res = await sendAndAwait(sharedDir, cmd, timeoutMs);
ok = ok && !!res;
ok = ok && !!(await sendAndAwait(sharedDir, 'update', timeoutMs));
if (ok) {
vscode.window.showInformationMessage(
`Added "${tokenText}" for ${instancePaths.length} instance(s) of ${modName}.`
);
} else {
vscode.window.showWarningMessage('No response from ModelSim (timeout or error).');
}
}
async function looksLikeVariable(
editor: vscode.TextEditor,
position: vscode.Position
): Promise<boolean> {
try {
const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
'vscode.executeDocumentSymbolProvider',
editor.document.uri
);
if (!symbols || symbols.length === 0) return true;
const flat = flattenSymbols(symbols);
const hit = flat
.filter(s => s.range.contains(position))
.sort((a, b) => a.range.end.compareTo(b.range.end))
.pop();
if (!hit) return true;
const variableKinds = new Set([
vscode.SymbolKind.Variable,
vscode.SymbolKind.Field,
vscode.SymbolKind.Constant,
vscode.SymbolKind.Property,
vscode.SymbolKind.EnumMember
]);
return variableKinds.has(hit.kind);
} catch {
return true;
}
}
function flattenSymbols(items: vscode.DocumentSymbol[]): vscode.DocumentSymbol[] {
const out: vscode.DocumentSymbol[] = [];
const walk = (arr: vscode.DocumentSymbol[]) => {
for (const s of arr) {
out.push(s);
if (s.children?.length) walk(s.children);
}
};
walk(items);
return out;
}