From cc6913f6cca54a5e3ea96f16a706d8be8010465c Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 11:03:41 +0200 Subject: [PATCH 1/7] feat: reuse existing terminal for Code_Aster runs --- src/RunAster.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/RunAster.ts b/src/RunAster.ts index 01cae87..2498c9d 100644 --- a/src/RunAster.ts +++ b/src/RunAster.ts @@ -34,14 +34,22 @@ export class RunAster { // return; // } - const simulationTerminal = vscode.window.createTerminal({ - name: 'code-aster runner', - cwd: fileDir, - }); + // Find existing terminal or create a new one + let simulationTerminal = vscode.window.terminals.find((t) => t.name === 'code-aster runner'); const cmd = `${alias} ${fileName}`; - simulationTerminal.show(); - simulationTerminal.sendText(cmd); + + if (simulationTerminal) { + simulationTerminal.show(); + simulationTerminal.sendText(`cd "${fileDir}" && ${cmd}`); + } else { + simulationTerminal = vscode.window.createTerminal({ + name: 'code-aster runner', + cwd: fileDir, + }); + simulationTerminal.show(); + simulationTerminal.sendText(cmd); + } } /** From 3f04e02893b08b367d70f29c718533ffa97ea569 Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 11:03:46 +0200 Subject: [PATCH 2/7] fix: change run icon to rocket --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 457bc47..46a5e07 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ { "command": "vs-code-aster.run-aster", "title": "Run with code_aster", - "icon": "$(run)" + "icon": "$(rocket)" }, { "command": "vs-code-aster.meshViewer", From 56c3801ea7630deb193e307e9137de5f09ac9d0c Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 11:17:18 +0200 Subject: [PATCH 3/7] feat: parse .mess output and surface diagnostics in Problems panel --- src/MessFileParser.ts | 90 +++++++++++++++++++++++++++++++++++++++++++ src/RunAster.ts | 70 +++++++++++++++++++++++++++++++++ src/extension.ts | 5 +++ 3 files changed, 165 insertions(+) create mode 100644 src/MessFileParser.ts diff --git a/src/MessFileParser.ts b/src/MessFileParser.ts new file mode 100644 index 0000000..11743fd --- /dev/null +++ b/src/MessFileParser.ts @@ -0,0 +1,90 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; + +interface CMDTAGMatch { + lineNumber: number; // 0-based + column?: number; +} + +export function parseMessFile( + content: string, + exportUri: vscode.Uri, + commUri: vscode.Uri | undefined +): Map { + const diagnostics = new Map(); + + const cmdtagRegex = /^\.\..*__(?:run|stg)\d+_(?:cmd|txt)(\d+)(?::(\d+))?/; + const tagRegex = /^<([AEF])>/; + + const lines = content.split('\n'); + let lastCMDTAG: CMDTAGMatch | undefined; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (!line.trim()) { + continue; + } + + const cmdtagMatch = line.match(cmdtagRegex); + if (cmdtagMatch) { + // Extract line number (1-based in the file, convert to 0-based) + const lineNum = parseInt(cmdtagMatch[1], 10) - 1; + const column = cmdtagMatch[2] ? parseInt(cmdtagMatch[2], 10) - 1 : 0; + lastCMDTAG = { lineNumber: lineNum, column }; + continue; + } + + const tagMatch = line.match(tagRegex); + if (tagMatch) { + const severity = tagMatch[1]; + const isSeverity = + severity === 'A' ? vscode.DiagnosticSeverity.Warning : vscode.DiagnosticSeverity.Error; + + // Collect the message (the tag line itself + subsequent text lines) + const messageLines: string[] = [line]; + for (let j = i + 1; j < lines.length; j++) { + const nextLine = lines[j]; + if (!nextLine.trim()) { + break; + } + if (nextLine.match(tagRegex) || nextLine.match(cmdtagRegex)) { + break; + } + messageLines.push(nextLine); + i = j; + } + + const message = messageLines.join('\n').trim(); + + // Determine target URI and position + let targetUri: vscode.Uri; + let range: vscode.Range; + + if (lastCMDTAG && commUri) { + // Attach to .comm file at the CMDTAG line + targetUri = commUri; + range = new vscode.Range( + new vscode.Position(lastCMDTAG.lineNumber, lastCMDTAG.column || 0), + new vscode.Position(lastCMDTAG.lineNumber, lastCMDTAG.column || 0) + ); + } else { + // Attach to .export file at start + targetUri = exportUri; + range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); + } + + const diagnostic = new vscode.Diagnostic(range, message, isSeverity); + diagnostic.source = 'code-aster'; + + const uriKey = targetUri.toString(); + if (!diagnostics.has(uriKey)) { + diagnostics.set(uriKey, []); + } + diagnostics.get(uriKey)!.push(diagnostic); + + lastCMDTAG = undefined; + } + } + + return diagnostics; +} diff --git a/src/RunAster.ts b/src/RunAster.ts index 2498c9d..3d29668 100644 --- a/src/RunAster.ts +++ b/src/RunAster.ts @@ -1,11 +1,23 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as fs from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; +import { parseMessFile } from './MessFileParser'; const execAsync = promisify(exec); export class RunAster { + private static diagnosticCollection: vscode.DiagnosticCollection | undefined; + private static messWatcher: vscode.FileSystemWatcher | undefined; + + /** + * Initialize the diagnostic collection (called from extension.ts) + */ + public static init(collection: vscode.DiagnosticCollection) { + RunAster.diagnosticCollection = collection; + } + /** * Runs code_aster on the selected .export file in the workspace. */ @@ -34,6 +46,40 @@ export class RunAster { // return; // } + // Parse .export file to find mess and comm file paths + let messPath: string | undefined; + let commPath: string | undefined; + try { + const exportContent = fs.readFileSync(filePath, 'utf-8'); + for (const line of exportContent.split('\n')) { + const parts = line.trim().split(/\s+/); + if (parts[0] === 'F' && parts[1] === 'mess') { + messPath = parts[2]; + } + if (parts[0] === 'F' && parts[1] === 'comm') { + commPath = parts[2]; + } + } + // Resolve relative paths against the export file directory + if (messPath && !path.isAbsolute(messPath)) { + messPath = path.join(fileDir, messPath); + } + if (commPath && !path.isAbsolute(commPath)) { + commPath = path.join(fileDir, commPath); + } + } catch (error) { + console.error('Failed to parse .export file:', error); + } + + // Clear previous diagnostics and dispose old watcher + if (RunAster.diagnosticCollection) { + RunAster.diagnosticCollection.clear(); + } + if (RunAster.messWatcher) { + RunAster.messWatcher.dispose(); + RunAster.messWatcher = undefined; + } + // Find existing terminal or create a new one let simulationTerminal = vscode.window.terminals.find((t) => t.name === 'code-aster runner'); @@ -50,6 +96,30 @@ export class RunAster { simulationTerminal.show(); simulationTerminal.sendText(cmd); } + + // Set up file watcher for .mess output file + if (messPath && RunAster.diagnosticCollection) { + const updateDiagnostics = () => { + try { + const messContent = fs.readFileSync(messPath!, 'utf-8'); + const commUri = commPath ? vscode.Uri.file(commPath) : undefined; + const diagnosticsMap = parseMessFile(messContent, editor.document.uri, commUri); + + // Clear and repopulate diagnostics + RunAster.diagnosticCollection!.clear(); + for (const [uriKey, diags] of diagnosticsMap) { + const uri = vscode.Uri.parse(uriKey); + RunAster.diagnosticCollection!.set(uri, diags); + } + } catch (error) { + console.error('Failed to parse .mess file:', error); + } + }; + + RunAster.messWatcher = vscode.workspace.createFileSystemWatcher(messPath); + RunAster.messWatcher.onDidCreate(updateDiagnostics); + RunAster.messWatcher.onDidChange(updateDiagnostics); + } } /** diff --git a/src/extension.ts b/src/extension.ts index 0d63061..bc61129 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -21,6 +21,11 @@ export async function activate(context: vscode.ExtensionContext) { // Set up telemetry context setTelemetryContext(context); + // Set up diagnostics collection for code-aster output parsing + const diagnosticCollection = vscode.languages.createDiagnosticCollection('code-aster'); + RunAster.init(diagnosticCollection); + context.subscriptions.push(diagnosticCollection); + const runaster = vscode.commands.registerCommand('vs-code-aster.run-aster', () => { RunAster.runCodeAster(); }); From 0bd6ffcbec9dca60b9da6746baf2b86af15ab8e1 Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 13:43:20 +0200 Subject: [PATCH 4/7] feat: capture all run errors regardless of .export config Replaces .mess file watching with tee'd capture of the full run output, and extends the parser to handle Python tracebacks, SyntaxErrors, Fatal Python errors, and MED/Fortran errors. Previously diagnostics only appeared when the .export declared `F mess`, and only for code_aster's box-drawn // messages; most real failures produced none. --- src/MessFileParser.ts | 90 ------------- src/OutputParser.ts | 291 ++++++++++++++++++++++++++++++++++++++++++ src/RunAster.ts | 72 ++++++----- 3 files changed, 330 insertions(+), 123 deletions(-) delete mode 100644 src/MessFileParser.ts create mode 100644 src/OutputParser.ts diff --git a/src/MessFileParser.ts b/src/MessFileParser.ts deleted file mode 100644 index 11743fd..0000000 --- a/src/MessFileParser.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; - -interface CMDTAGMatch { - lineNumber: number; // 0-based - column?: number; -} - -export function parseMessFile( - content: string, - exportUri: vscode.Uri, - commUri: vscode.Uri | undefined -): Map { - const diagnostics = new Map(); - - const cmdtagRegex = /^\.\..*__(?:run|stg)\d+_(?:cmd|txt)(\d+)(?::(\d+))?/; - const tagRegex = /^<([AEF])>/; - - const lines = content.split('\n'); - let lastCMDTAG: CMDTAGMatch | undefined; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (!line.trim()) { - continue; - } - - const cmdtagMatch = line.match(cmdtagRegex); - if (cmdtagMatch) { - // Extract line number (1-based in the file, convert to 0-based) - const lineNum = parseInt(cmdtagMatch[1], 10) - 1; - const column = cmdtagMatch[2] ? parseInt(cmdtagMatch[2], 10) - 1 : 0; - lastCMDTAG = { lineNumber: lineNum, column }; - continue; - } - - const tagMatch = line.match(tagRegex); - if (tagMatch) { - const severity = tagMatch[1]; - const isSeverity = - severity === 'A' ? vscode.DiagnosticSeverity.Warning : vscode.DiagnosticSeverity.Error; - - // Collect the message (the tag line itself + subsequent text lines) - const messageLines: string[] = [line]; - for (let j = i + 1; j < lines.length; j++) { - const nextLine = lines[j]; - if (!nextLine.trim()) { - break; - } - if (nextLine.match(tagRegex) || nextLine.match(cmdtagRegex)) { - break; - } - messageLines.push(nextLine); - i = j; - } - - const message = messageLines.join('\n').trim(); - - // Determine target URI and position - let targetUri: vscode.Uri; - let range: vscode.Range; - - if (lastCMDTAG && commUri) { - // Attach to .comm file at the CMDTAG line - targetUri = commUri; - range = new vscode.Range( - new vscode.Position(lastCMDTAG.lineNumber, lastCMDTAG.column || 0), - new vscode.Position(lastCMDTAG.lineNumber, lastCMDTAG.column || 0) - ); - } else { - // Attach to .export file at start - targetUri = exportUri; - range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)); - } - - const diagnostic = new vscode.Diagnostic(range, message, isSeverity); - diagnostic.source = 'code-aster'; - - const uriKey = targetUri.toString(); - if (!diagnostics.has(uriKey)) { - diagnostics.set(uriKey, []); - } - diagnostics.get(uriKey)!.push(diagnostic); - - lastCMDTAG = undefined; - } - } - - return diagnostics; -} diff --git a/src/OutputParser.ts b/src/OutputParser.ts new file mode 100644 index 0000000..8870c67 --- /dev/null +++ b/src/OutputParser.ts @@ -0,0 +1,291 @@ +import * as vscode from 'vscode'; + +interface CMDTAGMatch { + lineNumber: number; // 0-based + column?: number; +} + +/** + * Parses captured code_aster run output (stdout+stderr) and produces diagnostics. + * + * Handles: + * - Code_aster box-drawn `` (warning) / `` / `` (error) messages with CMDTAG mapping + * - Python tracebacks (NameError, AttributeError, RuntimeError, ...) + * - Python SyntaxError blocks (caret-style, no traceback header) + * - Fatal Python errors (e.g. Segmentation fault) + * - MED / Fortran-layer errors (`filename.c [N] : Erreur ...`) + */ +export function parseRunOutput( + content: string, + exportUri: vscode.Uri, + commFiles: Map +): Map { + const diagnostics = new Map(); + + const addDiagnostic = (uri: vscode.Uri, diag: vscode.Diagnostic) => { + const key = uri.toString(); + if (!diagnostics.has(key)) { + diagnostics.set(key, []); + } + diagnostics.get(key)!.push(diag); + }; + + const resolveSourceFile = (filePath: string): { uri: vscode.Uri; matched: boolean } => { + const match = filePath.match(/([^/\\]+?)(?:\.changed\.py)?$/); + if (match && commFiles.has(match[1])) { + return { uri: commFiles.get(match[1])!, matched: true }; + } + return { uri: exportUri, matched: false }; + }; + + const makeRange = (line: number, col = 0): vscode.Range => + new vscode.Range(new vscode.Position(line, col), new vscode.Position(line, col)); + + const lines = content.split('\n'); + + const cmdtagRegex = /^\.\. _(?:run|stg)\d+_(?:cmd|txt)(\d+)(?::(\d+))?/; + // Match ``, ``, `` followed by whitespace or `<` (next tag) — not `_` + // which appears in cave's status flags like `_ALARM`, `_ABNORMAL_ABORT`. + const tagRegex = /<([AEF])>(?!_)/; + const exceptionRegex = /^([A-Z][A-Za-z_]*(?:Error|Exception|Warning|Interrupt|Exit)):\s*(.*)/; + const fileRefRegex = /File "([^"]+)", line (\d+)/; + const medErrorRegex = /^(\w+\.c)\s*\[\d+\]\s*:\s*(.*)/i; + const currentFileRegex = /^__file__\s*=\s*r?["']([^"']+)["']/; + + let lastCMDTAG: CMDTAGMatch | undefined; + let currentCommUri: vscode.Uri | undefined; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (!line.trim()) { + continue; + } + + // Track the current active command file via `__file__ = r"..."` markers + // emitted by cave in each processed command file's preamble. + const currentFileMatch = line.match(currentFileRegex); + if (currentFileMatch) { + const basename = currentFileMatch[1].split(/[/\\]/).pop() || ''; + if (commFiles.has(basename)) { + currentCommUri = commFiles.get(basename); + } + continue; + } + + // CMDTAG marker: remember for the next code_aster box message + const cmdtagMatch = line.match(cmdtagRegex); + if (cmdtagMatch) { + const lineNum = parseInt(cmdtagMatch[1], 10) - 1; + const column = cmdtagMatch[2] ? parseInt(cmdtagMatch[2], 10) - 1 : 0; + lastCMDTAG = { lineNumber: lineNum, column }; + continue; + } + + // 1. Code_aster box message: `` / `` / `` + const tagMatch = line.match(tagRegex); + if (tagMatch) { + const severity = tagMatch[1]; + const diagSeverity = + severity === 'A' ? vscode.DiagnosticSeverity.Warning : vscode.DiagnosticSeverity.Error; + + const messageLines: string[] = [line]; + for (let j = i + 1; j < lines.length; j++) { + const nextLine = lines[j]; + if (!nextLine.trim()) { + break; + } + if (nextLine.match(tagRegex) || nextLine.match(cmdtagRegex)) { + break; + } + messageLines.push(nextLine); + i = j; + } + + const message = messageLines + .filter((l) => l.trim() && !/^[\s║╔╚═╗╝]+$/.test(l)) + .map((l) => l.replace(/[║╔╚╗╝]/g, '').trim()) + .filter((l) => l) + .join('\n') + .trim(); + + let targetUri: vscode.Uri; + let range: vscode.Range; + if (lastCMDTAG && currentCommUri) { + targetUri = currentCommUri; + range = makeRange(lastCMDTAG.lineNumber, lastCMDTAG.column || 0); + } else { + targetUri = exportUri; + range = makeRange(0); + } + + const diag = new vscode.Diagnostic(range, message, diagSeverity); + diag.source = 'code-aster'; + addDiagnostic(targetUri, diag); + lastCMDTAG = undefined; + continue; + } + + // 2. Python traceback: starts with `Traceback (most recent call last):` + if (line.trim() === 'Traceback (most recent call last):') { + const block: string[] = []; + let lastUserFileRef: { path: string; lineNumber: number } | undefined; + let exceptionLine = ''; + let j = i + 1; + for (; j < lines.length; j++) { + const cur = lines[j]; + if (!cur.trim()) { + continue; + } + const fileMatch = cur.match(fileRefRegex); + if (fileMatch) { + const filePath = fileMatch[1]; + const resolved = resolveSourceFile(filePath); + if (resolved.matched) { + lastUserFileRef = { path: filePath, lineNumber: parseInt(fileMatch[2], 10) - 1 }; + } else if (!lastUserFileRef) { + lastUserFileRef = { path: filePath, lineNumber: parseInt(fileMatch[2], 10) - 1 }; + } + block.push(cur); + continue; + } + if (cur.startsWith(' ')) { + block.push(cur); + continue; + } + const excMatch = cur.match(exceptionRegex); + if (excMatch) { + exceptionLine = cur.trim(); + break; + } + break; + } + i = j; + + if (exceptionLine) { + const targetUri = lastUserFileRef ? resolveSourceFile(lastUserFileRef.path).uri : exportUri; + const lineNum = lastUserFileRef ? lastUserFileRef.lineNumber : 0; + const message = [exceptionLine, ...block.slice(-4)].join('\n'); + const diag = new vscode.Diagnostic( + makeRange(lineNum), + message, + vscode.DiagnosticSeverity.Error + ); + diag.source = 'code-aster'; + addDiagnostic(targetUri, diag); + } + continue; + } + + // 3. Python SyntaxError (no Traceback header). Pattern: + // File "...", line N + // + // ^ + // SyntaxError: ... + const fileOnlyMatch = line.match(/^\s*File "([^"]+)", line (\d+)\s*$/); + if (fileOnlyMatch) { + const lookahead = lines.slice(i + 1, i + 6); + const hasCaret = lookahead.some((l) => /^\s*\^+\s*$/.test(l)); + const syntaxIdx = lookahead.findIndex((l) => /^SyntaxError:/.test(l.trim())); + if (hasCaret && syntaxIdx >= 0) { + const filePath = fileOnlyMatch[1]; + const lineNum = parseInt(fileOnlyMatch[2], 10) - 1; + const resolved = resolveSourceFile(filePath); + const message = [lookahead[syntaxIdx].trim(), ...lookahead.slice(0, syntaxIdx)] + .map((l) => l.replace(/\s+$/, '')) + .filter((l) => l) + .join('\n'); + const diag = new vscode.Diagnostic( + makeRange(lineNum), + message, + vscode.DiagnosticSeverity.Error + ); + diag.source = 'code-aster'; + addDiagnostic(resolved.uri, diag); + i += syntaxIdx + 1; + continue; + } + } + + // 4. Fatal Python error. The block is header + `Current thread ...:` + + // indented `File "..."` lines, ending at `Extension modules:` or a + // terminal status line. Scan the next ~40 lines for the deepest user + // source file reference. + if (line.startsWith('Fatal Python error:')) { + const header = line.trim(); + let userFileRef: { path: string; lineNumber: number } | undefined; + let j = i + 1; + const end = Math.min(lines.length, i + 40); + for (; j < end; j++) { + const cur = lines[j]; + if (/^Extension modules:/.test(cur) || /^Segmentation fault/.test(cur.trim())) { + break; + } + const fileMatch = cur.match(fileRefRegex); + if (fileMatch) { + const resolved = resolveSourceFile(fileMatch[1]); + if (resolved.matched) { + userFileRef = { path: fileMatch[1], lineNumber: parseInt(fileMatch[2], 10) - 1 }; + } else if (!userFileRef) { + userFileRef = { path: fileMatch[1], lineNumber: parseInt(fileMatch[2], 10) - 1 }; + } + } + } + i = j; + + const targetUri = userFileRef ? resolveSourceFile(userFileRef.path).uri : exportUri; + const lineNum = userFileRef ? userFileRef.lineNumber : 0; + const diag = new vscode.Diagnostic( + makeRange(lineNum), + header, + vscode.DiagnosticSeverity.Error + ); + diag.source = 'code-aster'; + addDiagnostic(targetUri, diag); + continue; + } + + // 5. MED / Fortran-layer error: `filename.c [N] : Erreur ...` + const medMatch = line.match(medErrorRegex); + if (medMatch && /erreur|error/i.test(line)) { + const messageLines = [line.trim()]; + let j = i + 1; + for (; j < lines.length && j < i + 5; j++) { + const cur = lines[j]; + if (cur.match(medErrorRegex)) { + messageLines.push(cur.trim()); + continue; + } + break; + } + i = j - 1; + + const diag = new vscode.Diagnostic( + makeRange(0), + messageLines.join('\n'), + vscode.DiagnosticSeverity.Error + ); + diag.source = 'code-aster'; + addDiagnostic(exportUri, diag); + continue; + } + } + + // Deduplicate: Python tracebacks are sometimes printed twice by the runtime + // (once to stderr, once tee'd to fort.6). Collapse identical diagnostics + // keyed by (line, first message line). + for (const [key, list] of diagnostics) { + const seen = new Set(); + const unique: vscode.Diagnostic[] = []; + for (const d of list) { + const firstLine = d.message.split('\n')[0]; + const sig = `${d.range.start.line}:${d.severity}:${firstLine}`; + if (!seen.has(sig)) { + seen.add(sig); + unique.push(d); + } + } + diagnostics.set(key, unique); + } + + return diagnostics; +} diff --git a/src/RunAster.ts b/src/RunAster.ts index 3d29668..4743b84 100644 --- a/src/RunAster.ts +++ b/src/RunAster.ts @@ -3,13 +3,15 @@ import * as path from 'path'; import * as fs from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { parseMessFile } from './MessFileParser'; +import { parseRunOutput } from './OutputParser'; const execAsync = promisify(exec); +const LOG_FILENAME = '.vscode-aster-run.log'; + export class RunAster { private static diagnosticCollection: vscode.DiagnosticCollection | undefined; - private static messWatcher: vscode.FileSystemWatcher | undefined; + private static logWatcher: vscode.FileSystemWatcher | undefined; /** * Initialize the diagnostic collection (called from extension.ts) @@ -46,27 +48,22 @@ export class RunAster { // return; // } - // Parse .export file to find mess and comm file paths - let messPath: string | undefined; - let commPath: string | undefined; + // Parse .export to find all `F comm` entries so we can map traceback + // paths (which reference `.changed.py` in a temp dir) back to + // the user's original files. + const commFiles = new Map(); try { const exportContent = fs.readFileSync(filePath, 'utf-8'); for (const line of exportContent.split('\n')) { const parts = line.trim().split(/\s+/); - if (parts[0] === 'F' && parts[1] === 'mess') { - messPath = parts[2]; - } - if (parts[0] === 'F' && parts[1] === 'comm') { - commPath = parts[2]; + if (parts[0] === 'F' && parts[1] === 'comm' && parts[2]) { + let commPath = parts[2]; + if (!path.isAbsolute(commPath)) { + commPath = path.join(fileDir, commPath); + } + commFiles.set(path.basename(commPath), vscode.Uri.file(commPath)); } } - // Resolve relative paths against the export file directory - if (messPath && !path.isAbsolute(messPath)) { - messPath = path.join(fileDir, messPath); - } - if (commPath && !path.isAbsolute(commPath)) { - commPath = path.join(fileDir, commPath); - } } catch (error) { console.error('Failed to parse .export file:', error); } @@ -75,16 +72,26 @@ export class RunAster { if (RunAster.diagnosticCollection) { RunAster.diagnosticCollection.clear(); } - if (RunAster.messWatcher) { - RunAster.messWatcher.dispose(); - RunAster.messWatcher = undefined; + if (RunAster.logWatcher) { + RunAster.logWatcher.dispose(); + RunAster.logWatcher = undefined; } - // Find existing terminal or create a new one - let simulationTerminal = vscode.window.terminals.find((t) => t.name === 'code-aster runner'); + // Capture all stdout/stderr via `tee` to a known log file so we can + // parse the full run output regardless of whether `F mess` is set in + // the `.export`. The user still sees live output in the terminal. + const logPath = path.join(fileDir, LOG_FILENAME); + try { + if (fs.existsSync(logPath)) { + fs.unlinkSync(logPath); + } + } catch (error) { + console.error('Failed to remove old log file:', error); + } - const cmd = `${alias} ${fileName}`; + const cmd = `${alias} ${fileName} 2>&1 | tee "${logPath}"`; + let simulationTerminal = vscode.window.terminals.find((t) => t.name === 'code-aster runner'); if (simulationTerminal) { simulationTerminal.show(); simulationTerminal.sendText(`cd "${fileDir}" && ${cmd}`); @@ -97,28 +104,27 @@ export class RunAster { simulationTerminal.sendText(cmd); } - // Set up file watcher for .mess output file - if (messPath && RunAster.diagnosticCollection) { + // Set up file watcher for the log file + if (RunAster.diagnosticCollection) { + const exportUri = editor.document.uri; const updateDiagnostics = () => { try { - const messContent = fs.readFileSync(messPath!, 'utf-8'); - const commUri = commPath ? vscode.Uri.file(commPath) : undefined; - const diagnosticsMap = parseMessFile(messContent, editor.document.uri, commUri); + const logContent = fs.readFileSync(logPath, 'utf-8'); + const diagnosticsMap = parseRunOutput(logContent, exportUri, commFiles); - // Clear and repopulate diagnostics RunAster.diagnosticCollection!.clear(); for (const [uriKey, diags] of diagnosticsMap) { const uri = vscode.Uri.parse(uriKey); RunAster.diagnosticCollection!.set(uri, diags); } } catch (error) { - console.error('Failed to parse .mess file:', error); + console.error('Failed to parse run output:', error); } }; - RunAster.messWatcher = vscode.workspace.createFileSystemWatcher(messPath); - RunAster.messWatcher.onDidCreate(updateDiagnostics); - RunAster.messWatcher.onDidChange(updateDiagnostics); + RunAster.logWatcher = vscode.workspace.createFileSystemWatcher(logPath); + RunAster.logWatcher.onDidCreate(updateDiagnostics); + RunAster.logWatcher.onDidChange(updateDiagnostics); } } From c10cce83d0515e01d49cdd946052cecd5f895f48 Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 13:43:29 +0200 Subject: [PATCH 5/7] feat: use shared blue rocket icon for run button and .export files Replaces the \$(rocket) codicon (which can't be tinted) with an SVG file so the run button picks up the blue #1057C8 that matches the new .export file icon. Consolidates into a single icone-rocket.svg used by both. --- media/images/icone-export-dark.svg | 8 -------- media/images/icone-export-light.svg | 8 -------- media/images/icone-rocket.svg | 1 + package.json | 9 ++++++--- 4 files changed, 7 insertions(+), 19 deletions(-) delete mode 100644 media/images/icone-export-dark.svg delete mode 100644 media/images/icone-export-light.svg create mode 100644 media/images/icone-rocket.svg diff --git a/media/images/icone-export-dark.svg b/media/images/icone-export-dark.svg deleted file mode 100644 index a66c0e3..0000000 --- a/media/images/icone-export-dark.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/media/images/icone-export-light.svg b/media/images/icone-export-light.svg deleted file mode 100644 index a66c0e3..0000000 --- a/media/images/icone-export-light.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/media/images/icone-rocket.svg b/media/images/icone-rocket.svg new file mode 100644 index 0000000..5664dff --- /dev/null +++ b/media/images/icone-rocket.svg @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index 46a5e07..a4b692f 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,10 @@ { "command": "vs-code-aster.run-aster", "title": "Run with code_aster", - "icon": "$(rocket)" + "icon": { + "light": "./media/images/icone-rocket.svg", + "dark": "./media/images/icone-rocket.svg" + } }, { "command": "vs-code-aster.meshViewer", @@ -92,8 +95,8 @@ ".export" ], "icon": { - "light": "./media/images/icone-export-light.svg", - "dark": "./media/images/icone-export-dark.svg" + "light": "./media/images/icone-rocket.svg", + "dark": "./media/images/icone-rocket.svg" } }, { From 1f56e7719dbf2178a33bece4546cdd682a53c399 Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 13:54:03 +0200 Subject: [PATCH 6/7] feat: use shared orange eye icon for mesh viewer button Replaces the \$(eye) codicon (which can't be tinted) with an SVG file so the mesh viewer button on .comm files picks up the orange #FF861D that matches the .med file icon. Also consolidates the duplicate MED icon files into a single icone-med.svg. --- media/images/icone-eye.svg | 1 + media/images/icone-med-light.svg | 6 ------ media/images/{icone-med-dark.svg => icone-med.svg} | 0 package.json | 9 ++++++--- 4 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 media/images/icone-eye.svg delete mode 100644 media/images/icone-med-light.svg rename media/images/{icone-med-dark.svg => icone-med.svg} (100%) diff --git a/media/images/icone-eye.svg b/media/images/icone-eye.svg new file mode 100644 index 0000000..3ab27ae --- /dev/null +++ b/media/images/icone-eye.svg @@ -0,0 +1 @@ + diff --git a/media/images/icone-med-light.svg b/media/images/icone-med-light.svg deleted file mode 100644 index e113e74..0000000 --- a/media/images/icone-med-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/media/images/icone-med-dark.svg b/media/images/icone-med.svg similarity index 100% rename from media/images/icone-med-dark.svg rename to media/images/icone-med.svg diff --git a/package.json b/package.json index a4b692f..94283ba 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,10 @@ { "command": "vs-code-aster.meshViewer", "title": "Open visualizer", - "icon": "$(eye)" + "icon": { + "light": "./media/images/icone-eye.svg", + "dark": "./media/images/icone-eye.svg" + } }, { "command": "vs-code-aster.restartLSPServer", @@ -110,8 +113,8 @@ ".rmed" ], "icon": { - "light": "./media/images/icone-med-light.svg", - "dark": "./media/images/icone-med-dark.svg" + "light": "./media/images/icone-med.svg", + "dark": "./media/images/icone-med.svg" } } ], From 06e7026d8e3e9d5ec2c428ed71baa331d262401f Mon Sep 17 00:00:00 2001 From: Ulysse Bouchet Date: Wed, 15 Apr 2026 14:01:15 +0200 Subject: [PATCH 7/7] [1.6.0] Bump version to 1.6.0 --- CHANGELOG.md | 10 ++++++++++ CITATION.cff | 2 +- README.md | 2 +- ROADMAP.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index add871c..2d84ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to the **VS Code Aster** extension will be documented in thi The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] - 2026-04-15 + +Run workflow overhaul: terminal reuse, automatic diagnostics in the Problems panel, and refreshed toolbar icons. + +### Added +- Run diagnostics: `` warnings and ``/`` errors from code_aster, Python tracebacks, `SyntaxError`s, fatal errors (e.g. segfaults), and MED/Fortran errors now surface automatically in the VS Code Problems panel — no `F mess` entry required in the `.export` +- Diagnostics attached to the originating `.comm`/`.com1` line when possible (via CMDTAG markers and Python tracebacks), and cleared between runs +- The existing `code-aster runner` terminal is now reused across runs instead of spawning a new one each time +- Colored toolbar icons: blue rocket for the run button (shared with the `.export` file icon) and orange eye for the mesh viewer button (matches the `.med` palette) + ## [1.5.4] - 2026-04-15 File icon improvements and language support for `.export` and MED files. diff --git a/CITATION.cff b/CITATION.cff index a3dffb4..f075ea4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,4 +1,4 @@ -cff-version: 1.5.4 +cff-version: 1.6.0 title: VS Code Aster message: >- If you use this software, please cite it using the diff --git a/README.md b/README.md index e072e84..7a24027 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Simvia Logo

- Version + Version License

diff --git a/ROADMAP.md b/ROADMAP.md index 949c6b7..49c3f2b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -4,7 +4,7 @@ The extension aims to reduce friction between modeling, validation, execution, and analysis by bringing **code_aster** native workflows into the editor. -## Current Capabilities (v1.5.4) +## Current Capabilities (v1.6.0) - `.export` file generator - 3D mesh viewer diff --git a/package-lock.json b/package-lock.json index f255755..bc4d48e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vs-code-aster", - "version": "1.5.4", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vs-code-aster", - "version": "1.5.4", + "version": "1.6.0", "license": "GPL-3.0", "dependencies": { "@tailwindcss/cli": "^4.1.17", diff --git a/package.json b/package.json index 94283ba..57a32c5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vs-code-aster", "displayName": "VS Code Aster", - "version": "1.5.4", + "version": "1.6.0", "description": "VS Code extension for code_aster", "publisher": "simvia", "license": "GPL-3.0",