Przeglądaj źródła

Compiler UI: editor toolbar buttons and Modula-2 output channel

- Build/Run buttons in the editor tab-bar toolbar for .mod/.def
  files (editor-only visibility, verified no leakage into Outline)
- build and run output goes to a dedicated Modula-2 output channel
  (command line plus full output); channel auto-opens on run and
  on build failure
Eric Streit 1 tydzień temu
rodzic
commit
502e3ab4f2

+ 1 - 0
apps/browser/package.json

@@ -12,6 +12,7 @@
     "@theia/monaco": "1.75.0",
     "@theia/navigator": "1.75.0",
     "@theia/outline-view": "1.75.0",
+    "@theia/output": "1.75.0",
     "@theia/plugin-ext-vscode": "1.75.0",
     "@theia/preferences": "1.75.0",
     "@theia/process": "1.75.0",

+ 1 - 0
apps/electron/package.json

@@ -17,6 +17,7 @@
     "@theia/monaco": "1.75.0",
     "@theia/navigator": "1.75.0",
     "@theia/outline-view": "1.75.0",
+    "@theia/output": "1.75.0",
     "@theia/plugin-ext-vscode": "1.75.0",
     "@theia/preferences": "1.75.0",
     "@theia/process": "1.75.0",

+ 24 - 6
extensions/modula2-compiler/package.json

@@ -2,20 +2,38 @@
   "name": "modula2-compiler",
   "displayName": "Modula-2 Compiler Support",
   "version": "0.2.0",
-  "keywords": ["theia-extension"],
-  "files": ["lib", "src"],
+  "keywords": [
+    "theia-extension"
+  ],
+  "files": [
+    "lib",
+    "src"
+  ],
   "dependencies": {
     "@theia/core": "1.75.0",
     "@theia/editor": "1.75.0",
     "@theia/filesystem": "1.75.0",
     "@theia/markers": "1.75.0",
+    "@theia/output": "1.75.0",
     "@theia/task": "1.75.0",
     "@theia/workspace": "1.75.0"
   },
-  "devDependencies": {"rimraf": "^6.0.1", "typescript": "^5.9.2"},
-  "scripts": {"clean": "rimraf lib", "build": "tsc -p tsconfig.json", "watch": "tsc -p tsconfig.json --watch", "prepare": "yarn clean && yarn build"},
+  "devDependencies": {
+    "rimraf": "^6.0.1",
+    "typescript": "^5.9.2"
+  },
+  "scripts": {
+    "clean": "rimraf lib",
+    "build": "tsc -p tsconfig.json",
+    "watch": "tsc -p tsconfig.json --watch",
+    "prepare": "yarn clean && yarn build"
+  },
   "theiaExtensions": [
-    {"frontend": "lib/browser/modula2-compiler-frontend-module"},
-    {"backend": "lib/node/modula2-compiler-backend-module"}
+    {
+      "frontend": "lib/browser/modula2-compiler-frontend-module"
+    },
+    {
+      "backend": "lib/node/modula2-compiler-backend-module"
+    }
   ]
 }

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

@@ -3,6 +3,10 @@ import { inject, injectable } from '@theia/core/shared/inversify';
 import {
   Command, CommandContribution, CommandRegistry, MessageService,
 } from '@theia/core/lib/common';
+import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
+import { codicon } from '@theia/core/lib/browser';
+import { Widget } from '@theia/core/lib/browser';
+import { EditorWidget } from '@theia/editor/lib/browser';
 import { RemoteConnectionProvider, ServiceConnectionProvider } from '@theia/core/lib/browser/messaging/service-connection-provider';
 import { FileService } from '@theia/filesystem/lib/browser/file-service';
 import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
@@ -11,6 +15,7 @@ import { ProblemManager } from '@theia/markers/lib/browser/problem/problem-manag
 import { Diagnostic, DiagnosticSeverity } from '@theia/core/shared/vscode-languageserver-protocol';
 import { PreferenceContribution } from '@theia/core/lib/common/preferences/preference-schema';
 import { PreferenceService } from '@theia/core/lib/common/preferences/preference-service';
+import { OutputChannelManager } from '@theia/output/lib/browser/output-channel';
 import { TaskContribution } from '@theia/task/lib/browser/task-contribution';
 import URI from '@theia/core/lib/common/uri';
 import {
@@ -52,6 +57,7 @@ class CompilerCommands implements CommandContribution {
   @inject(MessageService) protected readonly messages: MessageService;
   @inject(ProblemManager) protected readonly problems: ProblemManager;
   @inject(PreferenceService) protected readonly prefs: PreferenceService;
+  @inject(OutputChannelManager) protected readonly outputChannels: OutputChannelManager;
   /** URIs currently carrying our markers, so stale ones can be cleared. */
   protected readonly markedUris = new Set<string>();
 
@@ -132,12 +138,21 @@ class CompilerCommands implements CommandContribution {
     return `${errors} error(s), ${warnings} warning(s). Command: ${result.command}`;
   }
 
+  /** Append the exact command plus its output to the Modula-2 channel. */
+  protected logCommand(command: string, output: string): void {
+    const channel = this.outputChannels.getChannel('Modula-2');
+    channel.appendLine(`$ ${command}`);
+    if (output.length > 0) channel.append(output.endsWith('\n') ? output : output + '\n');
+  }
+
   protected async build(project: Modula2Project): Promise<void> {
     const result = await this.compiler.build(project);
     this.setProblemMarkers(project, result.diagnostics);
+    this.logCommand(result.command, result.output);
     if (result.success) {
       this.messages.info(`Modula-2: build succeeded. ${this.summarize(result)}`);
     } else {
+      this.outputChannels.getChannel('Modula-2').show();
       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. ${this.summarize(result)}${first ? '\n' + first : ''}`);
@@ -167,14 +182,16 @@ class CompilerCommands implements CommandContribution {
   protected async run(project: Modula2Project): Promise<void> {
     const result = await this.compiler.run(project);
     this.setProblemMarkers(project, result.diagnostics);
+    // The build step already logged its command; log the run itself.
+    this.logCommand(result.command, result.output);
+    this.outputChannels.getChannel('Modula-2').show();
     if (!result.success && result.diagnostics.length > 0) {
       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;
-    this.messages.info(`Modula-2: program exited with code ${result.exitCode}.\n${output}`);
+    this.messages.info(`Modula-2: program exited with code ${result.exitCode}.`);
   }
 
   protected async detect(): Promise<void> {
@@ -188,6 +205,36 @@ class CompilerCommands implements CommandContribution {
   }
 }
 
+@injectable()
+class CompilerToolbar implements TabBarToolbarContribution {
+  registerToolbarItems(registry: TabBarToolbarRegistry): void {
+    // Editor toolbar buttons, visible only for Modula-2 files in editors.
+    // The `when` clause follows the active file; `isVisible` additionally
+    // restricts to editor widgets so the buttons do not leak into the
+    // Outline and other side views sharing the same tab-bar toolbar.
+    const when = 'resourceExtname == .mod || resourceExtname == .def';
+    const editorsOnly = (widget?: Widget): boolean => widget instanceof EditorWidget;
+    registry.registerItem({
+      id: 'modula2.build.toolbar',
+      command: Commands.build.id,
+      icon: codicon('tools'),
+      tooltip: 'Modula-2: Build',
+      when,
+      isVisible: editorsOnly,
+      priority: 1,
+    });
+    registry.registerItem({
+      id: 'modula2.run.toolbar',
+      command: Commands.run.id,
+      icon: codicon('play'),
+      tooltip: 'Modula-2: Run',
+      when,
+      isVisible: editorsOnly,
+      priority: 2,
+    });
+  }
+}
+
 export default new ContainerModule(bind => {
   bind(PreferenceContribution).toConstantValue({ schema: modula2PreferenceSchema });
   bind(Modula2Compiler).toDynamicValue(ctx => {
@@ -195,6 +242,7 @@ export default new ContainerModule(bind => {
     return provider.createProxy<Modula2Compiler>(Modula2CompilerServicePath);
   }).inSingletonScope();
   bind(CommandContribution).to(CompilerCommands).inSingletonScope();
+  bind(TabBarToolbarContribution).to(CompilerToolbar).inSingletonScope();
   bind(Modula2TaskProvider).toSelf().inSingletonScope();
   bind(Modula2TaskResolver).toSelf().inSingletonScope();
   bind(TaskContribution).to(Modula2TaskContribution).inSingletonScope();