|
|
@@ -6,9 +6,11 @@ import {
|
|
|
} from '@theia/core/lib/common';
|
|
|
import { WidgetFactory, bindViewContribution, FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
|
import { QuickInputService } from '@theia/core/lib/common/quick-pick-service';
|
|
|
+import { StorageService } from '@theia/core/lib/browser/storage-service';
|
|
|
import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
|
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
|
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
|
|
|
+import { WorkspaceCommands } from '@theia/workspace/lib/browser/workspace-commands';
|
|
|
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
|
|
|
import URI from '@theia/core/lib/common/uri';
|
|
|
import {
|
|
|
@@ -17,8 +19,8 @@ import {
|
|
|
scaffoldFiles, validateProjectFile,
|
|
|
} from './modula2-project';
|
|
|
import {
|
|
|
- MODULA2_EXPLORER_ID, Modula2ExplorerCommands, Modula2ExplorerContribution,
|
|
|
- Modula2ExplorerMenus, createModula2ExplorerWidget,
|
|
|
+ MODULA2_EXPLORER_ID, Modula2ExplorerCommands, Modula2ExplorerContainerFactory,
|
|
|
+ Modula2ExplorerContribution, Modula2ExplorerMenus, createModula2ExplorerWidget,
|
|
|
} from './modula2-explorer-widget';
|
|
|
|
|
|
const CATEGORY = 'Modula-2';
|
|
|
@@ -27,6 +29,9 @@ const Commands = {
|
|
|
newModule: Command.toLocalizedCommand({ id: 'modula2.newModule', label: 'New Module...', category: CATEGORY }),
|
|
|
newDefModule: Command.toLocalizedCommand({ id: 'modula2.newDefModule', label: 'New Definition Module...', category: CATEGORY }),
|
|
|
newProject: Command.toLocalizedCommand({ id: 'modula2.newProject', label: 'New Modula-2 Project...', category: CATEGORY }),
|
|
|
+ openProject: Command.toLocalizedCommand({ id: 'modula2.openProject', label: 'Open Project...', category: CATEGORY }),
|
|
|
+ switchProject: Command.toLocalizedCommand({ id: 'modula2.switchProject', label: 'Switch Project...', category: CATEGORY }),
|
|
|
+ closeProject: Command.toLocalizedCommand({ id: 'modula2.closeProject', label: 'Close Project', category: CATEGORY }),
|
|
|
validate: Command.toLocalizedCommand({ id: 'modula2.validateProject', label: 'Validate Project', category: CATEGORY }),
|
|
|
};
|
|
|
|
|
|
@@ -37,11 +42,16 @@ class ProjectCommands implements CommandContribution {
|
|
|
@inject(EditorManager) protected readonly editors: EditorManager;
|
|
|
@inject(MessageService) protected readonly messages: MessageService;
|
|
|
@inject(QuickInputService) protected readonly quickInput: QuickInputService;
|
|
|
+ @inject(CommandRegistry) protected readonly commands: CommandRegistry;
|
|
|
+ @inject(StorageService) protected readonly storage: StorageService;
|
|
|
|
|
|
registerCommands(commands: CommandRegistry): void {
|
|
|
commands.registerCommand(Commands.newModule, { execute: () => this.newModule(false) });
|
|
|
commands.registerCommand(Commands.newDefModule, { execute: () => this.newModule(true) });
|
|
|
commands.registerCommand(Commands.newProject, { execute: () => this.newProject() });
|
|
|
+ commands.registerCommand(Commands.openProject, { execute: () => this.openProject() });
|
|
|
+ commands.registerCommand(Commands.switchProject, { execute: () => this.switchProject() });
|
|
|
+ commands.registerCommand(Commands.closeProject, { execute: () => this.closeProject() });
|
|
|
commands.registerCommand(Commands.validate, { execute: () => this.validateProject() });
|
|
|
}
|
|
|
|
|
|
@@ -93,12 +103,13 @@ class ProjectCommands implements CommandContribution {
|
|
|
if (!defOnly) {
|
|
|
const kind = await this.quickInput.showQuickPick(
|
|
|
[{ label: 'Implementation + definition', description: 'MyModule.mod and MyModule.def', value: 'both' },
|
|
|
- { label: 'Implementation only', description: 'MyModule.mod', value: 'mod' }],
|
|
|
+ { label: 'Implementation only', description: 'MyModule.mod', value: 'mod' },
|
|
|
+ { label: 'Definition only', description: 'MyModule.def', value: 'def' }],
|
|
|
{ placeholder: 'What to create?' },
|
|
|
);
|
|
|
if (!kind) return;
|
|
|
- makeDef = kind.value === 'both';
|
|
|
- makeMod = true;
|
|
|
+ makeDef = kind.value !== 'mod';
|
|
|
+ makeMod = kind.value !== 'def';
|
|
|
}
|
|
|
const choices = await this.sourceDirChoices(root);
|
|
|
let dir = choices[0];
|
|
|
@@ -180,6 +191,68 @@ class ProjectCommands implements CommandContribution {
|
|
|
this.messages.info(`Modula-2: created ${template.label} "${name}" in ${where}. Open modula2.json to review, then Modula-2: Build.`);
|
|
|
}
|
|
|
|
|
|
+ /** Open a project folder (delegates to the workspace folder dialog). */
|
|
|
+ protected async openProject(): Promise<void> {
|
|
|
+ await this.commands.executeCommand(WorkspaceCommands.OPEN_FOLDER.id);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Switch to a recently opened project (delegates to the recent picker). */
|
|
|
+ protected async switchProject(): Promise<void> {
|
|
|
+ await this.recordCurrentProject();
|
|
|
+ const recent = await this.recentProjects();
|
|
|
+ const picks = recent.map(r => ({
|
|
|
+ label: r.name, description: r.path, value: r.path,
|
|
|
+ }));
|
|
|
+ picks.push({ label: 'Browse for folder...', description: 'Open any folder as a project', value: '' });
|
|
|
+ const choice = await this.quickInput.showQuickPick(picks, { placeholder: 'Switch to project' });
|
|
|
+ if (!choice) return;
|
|
|
+ if (!choice.value) {
|
|
|
+ await this.commands.executeCommand(WorkspaceCommands.OPEN_FOLDER.id);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.workspace.open(new URI(choice.value));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Remembered Modula-2 projects (most recent first, max 12). */
|
|
|
+ protected async recentProjects(): Promise<{ name: string; path: string }[]> {
|
|
|
+ try {
|
|
|
+ const raw = await this.storage.getData<{ name: string; path: string }[]>('modula2.recentProjects', []);
|
|
|
+ if (!Array.isArray(raw)) return [];
|
|
|
+ const live: { name: string; path: string }[] = [];
|
|
|
+ for (const r of raw) {
|
|
|
+ if (!r || typeof r.name !== 'string' || typeof r.path !== 'string') continue;
|
|
|
+ try {
|
|
|
+ if (await this.files.exists(new URI(r.path))) live.push({ name: r.name, path: r.path });
|
|
|
+ } catch { /* drop dead entries */ }
|
|
|
+ }
|
|
|
+ return live.slice(0, 12);
|
|
|
+ } catch {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Record the current workspace when it holds a Modula-2 project. */
|
|
|
+ protected async recordCurrentProject(): Promise<void> {
|
|
|
+ try {
|
|
|
+ const roots = await this.workspace.roots;
|
|
|
+ if (roots.length === 0) return;
|
|
|
+ const root = roots[0].resource;
|
|
|
+ const raw = await this.files.read(root.resolve('modula2.json')).catch(() => undefined);
|
|
|
+ if (!raw) return;
|
|
|
+ const json = JSON.parse(raw.value) as { name?: unknown };
|
|
|
+ if (typeof json.name !== 'string' || json.name.length === 0) return;
|
|
|
+ const entry = { name: json.name, path: root.toString() };
|
|
|
+ const prev = await this.recentProjects();
|
|
|
+ const next = [entry, ...prev.filter(r => r.path !== entry.path)].slice(0, 12);
|
|
|
+ await this.storage.setData('modula2.recentProjects', next);
|
|
|
+ } catch { /* best-effort history */ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Close the current project (delegates to workspace close). */
|
|
|
+ protected async closeProject(): Promise<void> {
|
|
|
+ await this.commands.executeCommand(WorkspaceCommands.CLOSE.id);
|
|
|
+ }
|
|
|
+
|
|
|
protected async validateProject(): Promise<void> {
|
|
|
const root = await this.root();
|
|
|
if (!root) return;
|
|
|
@@ -230,6 +303,14 @@ class ProjectMenus implements MenuContribution {
|
|
|
menus.registerMenuAction(submenu, { commandId: Commands.newDefModule.id, order: '21' });
|
|
|
menus.registerMenuAction(submenu, { commandId: Commands.newProject.id, order: '22' });
|
|
|
menus.registerMenuAction(submenu, { commandId: Commands.validate.id, order: '23' });
|
|
|
+ const project = ['menubar', 'project'];
|
|
|
+ menus.registerSubmenu(project, 'Project');
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.newProject.id, order: '10' });
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.newModule.id, order: '11' });
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.newDefModule.id, order: '12' });
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.openProject.id, order: '13' });
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.switchProject.id, order: '14' });
|
|
|
+ menus.registerMenuAction(project, { commandId: Commands.closeProject.id, order: '15' });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -242,6 +323,8 @@ export default new ContainerModule(bind => {
|
|
|
id: MODULA2_EXPLORER_ID,
|
|
|
createWidget: () => createModula2ExplorerWidget(context.container),
|
|
|
}));
|
|
|
+ bind(Modula2ExplorerContainerFactory).toSelf().inSingletonScope();
|
|
|
+ bind(WidgetFactory).toService(Modula2ExplorerContainerFactory);
|
|
|
bind(CommandContribution).to(Modula2ExplorerCommands).inSingletonScope();
|
|
|
bind(MenuContribution).to(Modula2ExplorerMenus).inSingletonScope();
|
|
|
});
|