Procházet zdrojové kódy

Phase 3 (2/5): gm2 diagnostics in the Problems view

Compiler frontend publishes BuildDiagnostic[] as ProblemManager markers (owner modula2-gm2, click-to-navigate, stale markers cleared on rebuild/clean); run/clean paths updated to keep markers in sync.

Verified end-to-end in a headless browser: Modula-2: Build on a broken project shows the error notification and 2 Problems markers; zero console/page errors.
Eric Streit před 2 týdny
rodič
revize
b7a459a2e1

+ 1 - 0
extensions/modula2-compiler/package.json

@@ -8,6 +8,7 @@
     "@theia/core": "1.75.0",
     "@theia/editor": "1.75.0",
     "@theia/filesystem": "1.75.0",
+    "@theia/markers": "1.75.0",
     "@theia/workspace": "1.75.0"
   },
   "devDependencies": {"rimraf": "^6.0.1", "typescript": "^5.9.2"},

+ 56 - 2
extensions/modula2-compiler/src/browser/modula2-compiler-frontend-module.ts

@@ -7,13 +7,27 @@ import { RemoteConnectionProvider, ServiceConnectionProvider } from '@theia/core
 import { FileService } from '@theia/filesystem/lib/browser/file-service';
 import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
 import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
+import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manager';
+import { Diagnostic, DiagnosticSeverity } from '@theia/core/shared/vscode-languageserver-protocol';
+import URI from '@theia/core/lib/common/uri';
 import {
-  BuildResult, Modula2Compiler, Modula2CompilerServicePath,
+  BuildDiagnostic, BuildResult, Modula2Compiler, Modula2CompilerServicePath,
   Modula2Project, parseProjectFile,
 } from '../common/compiler';
 
 const CATEGORY = 'Modula-2';
 
+/** Marker owner for gm2 build diagnostics shown in the Problems view. */
+const MARKER_OWNER = 'modula2-gm2';
+
+function severityOf(d: BuildDiagnostic): DiagnosticSeverity {
+  switch (d.severity) {
+    case 'error': return DiagnosticSeverity.Error;
+    case 'warning': return DiagnosticSeverity.Warning;
+    default: return DiagnosticSeverity.Information;
+  }
+}
+
 const Commands = {
   build: Command.toLocalizedCommand({ id: 'modula2.build', label: 'Build', category: CATEGORY }),
   buildFile: Command.toLocalizedCommand({ id: 'modula2.buildFile', label: 'Build File', category: CATEGORY }),
@@ -30,6 +44,9 @@ class CompilerCommands implements CommandContribution {
   @inject(WorkspaceService) protected readonly workspace: WorkspaceService;
   @inject(EditorManager) protected readonly editors: EditorManager;
   @inject(MessageService) protected readonly messages: MessageService;
+  @inject(ProblemManager) protected readonly problems: ProblemManager;
+  /** URIs currently carrying our markers, so stale ones can be cleared. */
+  protected readonly markedUris = new Set<string>();
 
   registerCommands(commands: CommandRegistry): void {
     commands.registerCommand(Commands.detect, { execute: () => this.detect() });
@@ -70,6 +87,38 @@ class CompilerCommands implements CommandContribution {
     }
   }
 
+  /** Publish build diagnostics as Problems markers (click-to-navigate). */
+  protected setProblemMarkers(project: Modula2Project, diagnostics: BuildDiagnostic[]): void {
+    this.clearProblemMarkers();
+    const rootUri = new URI(`file://${project.root}`);
+    const byFile = new Map<string, Diagnostic[]>();
+    for (const d of diagnostics) {
+      const uri = d.file.startsWith('/') ? new URI(`file://${d.file}`) : rootUri.resolve(d.file);
+      const list = byFile.get(uri.toString()) ?? [];
+      list.push({
+        range: {
+          start: { line: d.line, character: d.character },
+          end: { line: d.line, character: d.character + 1 },
+        },
+        severity: severityOf(d),
+        message: d.message,
+        source: 'gm2',
+      });
+      byFile.set(uri.toString(), list);
+    }
+    for (const [key, list] of byFile) {
+      this.problems.setMarkers(new URI(key), MARKER_OWNER, list);
+      this.markedUris.add(key);
+    }
+  }
+
+  protected clearProblemMarkers(): void {
+    for (const key of this.markedUris) {
+      this.problems.cleanAllMarkers(new URI(key));
+    }
+    this.markedUris.clear();
+  }
+
   protected summarize(result: BuildResult): string {
     const errors = result.diagnostics.filter(d => d.severity === 'error').length;
     const warnings = result.diagnostics.filter(d => d.severity === 'warning').length;
@@ -78,6 +127,7 @@ class CompilerCommands implements CommandContribution {
 
   protected async build(project: Modula2Project): Promise<void> {
     const result = await this.compiler.build(project);
+    this.setProblemMarkers(project, result.diagnostics);
     if (result.success) {
       this.messages.info(`Modula-2: build succeeded. ${this.summarize(result)}`);
     } else {
@@ -103,13 +153,17 @@ class CompilerCommands implements CommandContribution {
 
   protected async clean(project: Modula2Project): Promise<void> {
     await this.compiler.clean(project);
+    this.clearProblemMarkers();
     this.messages.info('Modula-2: cleaned build outputs.');
   }
 
   protected async run(project: Modula2Project): Promise<void> {
     const result = await this.compiler.run(project);
+    this.setProblemMarkers(project, result.diagnostics);
     if (!result.success && result.diagnostics.length > 0) {
-      await this.build(project);
+      const first = result.diagnostics.slice(0, 3)
+        .map(d => `${d.file}:${d.line + 1}:${d.character + 1}: ${d.message}`).join('\n');
+      this.messages.error(`Modula-2: build failed.\n${first}`);
       return;
     }
     const output = result.output.length > 2000 ? result.output.slice(0, 2000) + '\n…' : result.output;