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 = {}; export async function requestModuleTree( sharedDir: string, topScope: string, timeoutMs: number ): Promise> { const tree: Record = {}; 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; // { ()} const moduleTree: Record = {}; 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('sharedDir') || '.'; const topScope = cfg.get('topScope') || 'sim:/test/'; // keep in sync with package.json const timeoutMs = cfg.get('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 { try { const symbols = await vscode.commands.executeCommand( '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; }