215 lines
7.3 KiB
TypeScript
215 lines
7.3 KiB
TypeScript
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}" doesn’t 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 'That’s 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;
|
||
}
|