|
|
@@ -6,16 +6,22 @@ import {
|
|
|
ExpandableTreeNode, NodeProps, SelectableTreeNode,
|
|
|
TreeModel, TreeNode, TreeProps, TreeWidget, WidgetFactory, WidgetManager,
|
|
|
} from '@theia/core/lib/browser';
|
|
|
-import { Command, CommandContribution, CommandRegistry } from '@theia/core/lib/common';
|
|
|
+import { Command, CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry, MessageService } from '@theia/core/lib/common';
|
|
|
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 { QuickInputService } from '@theia/core/lib/common/quick-pick-service';
|
|
|
+import { BinaryBuffer } from '@theia/core/lib/common/buffer';
|
|
|
import URI from '@theia/core/lib/common/uri';
|
|
|
import { Message } from '@lumino/messaging';
|
|
|
import { groupExplorerFiles } from './modula2-explorer-model';
|
|
|
+import { isValidModuleName, renderDefModule, renderImplModule } from './modula2-project';
|
|
|
|
|
|
export const MODULA2_EXPLORER_ID = 'modula2-explorer';
|
|
|
|
|
|
+/** Context menu path rendered by the explorer tree on right-click. */
|
|
|
+export const MODULA2_EXPLORER_CONTEXT_MENU = ['modula2-explorer-context-menu'];
|
|
|
+
|
|
|
export interface Modula2TreeNode extends CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode {
|
|
|
kind: 'project' | 'group' | 'file';
|
|
|
uri?: string;
|
|
|
@@ -227,9 +233,18 @@ export const RefreshExplorerCommand: Command = Command.toLocalizedCommand({
|
|
|
id: 'modula2.refreshExplorer', label: 'Refresh Project Explorer', category: 'Modula-2',
|
|
|
});
|
|
|
|
|
|
+export const NewModuleHereCommand: Command = Command.toLocalizedCommand({
|
|
|
+ id: 'modula2.newModuleHere', label: 'New Module Here...', category: 'Modula-2',
|
|
|
+});
|
|
|
+
|
|
|
@injectable()
|
|
|
export class Modula2ExplorerCommands implements CommandContribution {
|
|
|
@inject(WidgetManager) protected readonly widgets: WidgetManager;
|
|
|
+ @inject(FileService) protected readonly files: FileService;
|
|
|
+ @inject(WorkspaceService) protected readonly workspace: WorkspaceService;
|
|
|
+ @inject(EditorManager) protected readonly editors: EditorManager;
|
|
|
+ @inject(MessageService) protected readonly messages: MessageService;
|
|
|
+ @inject(QuickInputService) protected readonly quickInput: QuickInputService;
|
|
|
|
|
|
registerCommands(commands: CommandRegistry): void {
|
|
|
commands.registerCommand(RefreshExplorerCommand, {
|
|
|
@@ -239,10 +254,78 @@ export class Modula2ExplorerCommands implements CommandContribution {
|
|
|
}
|
|
|
},
|
|
|
});
|
|
|
+ commands.registerCommand(NewModuleHereCommand, { execute: () => this.newModuleHere() });
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Folder of the selected explorer file, or undefined when nothing useful is selected. */
|
|
|
+ protected selectedDir(): URI | undefined {
|
|
|
+ for (const widget of this.widgets.getWidgets(MODULA2_EXPLORER_ID)) {
|
|
|
+ const view = widget as Modula2ExplorerWidget;
|
|
|
+ const node = view.model.selectedNodes[0] as Modula2TreeNode | undefined;
|
|
|
+ if (node?.kind === 'file' && node.uri) {
|
|
|
+ const uri = new URI(node.uri);
|
|
|
+ return uri.parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected async newModuleHere(): Promise<void> {
|
|
|
+ const dir = this.selectedDir();
|
|
|
+ if (!dir) {
|
|
|
+ this.messages.warn('Modula-2: select a file in the Modula-2 Project view first, or use Modula-2: New Module.');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const raw = await this.quickInput.input({
|
|
|
+ prompt: 'Module name (a Modula-2 identifier, e.g. MyModule)',
|
|
|
+ placeHolder: 'MyModule',
|
|
|
+ validateInput: async value => isValidModuleName(value.trim()) ?? null,
|
|
|
+ });
|
|
|
+ if (raw === undefined) return;
|
|
|
+ const name = raw.trim();
|
|
|
+ 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' }],
|
|
|
+ { placeholder: 'What to create?' },
|
|
|
+ );
|
|
|
+ if (!kind) return;
|
|
|
+ const created: string[] = [];
|
|
|
+ const write = async (file: string, content: string): Promise<void> => {
|
|
|
+ const uri = dir.resolve(file);
|
|
|
+ try {
|
|
|
+ if (await this.files.exists(uri)) {
|
|
|
+ this.messages.warn(`Modula-2: ${file} already exists, skipped.`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await this.files.writeFile(uri, BinaryBuffer.fromString(content));
|
|
|
+ created.push(file);
|
|
|
+ } catch (error) {
|
|
|
+ this.messages.error(`Modula-2: cannot write ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ await write(`${name}.mod`, renderImplModule(name));
|
|
|
+ if (kind.value === 'both') await write(`${name}.def`, renderDefModule(name));
|
|
|
+ if (created.length === 0) return;
|
|
|
+ this.messages.info(`Modula-2: created ${created.join(', ')}.`);
|
|
|
+ try {
|
|
|
+ await this.editors.open(dir.resolve(created[0]), { mode: 'activate' });
|
|
|
+ } catch { /* created is enough */ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+export class Modula2ExplorerMenus implements MenuContribution {
|
|
|
+ registerMenus(menus: MenuModelRegistry): void {
|
|
|
+ const path = MODULA2_EXPLORER_CONTEXT_MENU;
|
|
|
+ menus.registerMenuAction(path, { commandId: NewModuleHereCommand.id, order: '10' });
|
|
|
+ menus.registerMenuAction(path, { commandId: 'modula2.build', order: '20' });
|
|
|
+ menus.registerMenuAction(path, { commandId: RefreshExplorerCommand.id, order: '30' });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
export function createModula2ExplorerWidget(parent: interfaces.Container): Modula2ExplorerWidget {
|
|
|
- const child = createTreeContainer(parent, { widget: Modula2ExplorerWidget });
|
|
|
+ const child = createTreeContainer(parent, {
|
|
|
+ widget: Modula2ExplorerWidget,
|
|
|
+ props: { contextMenuPath: MODULA2_EXPLORER_CONTEXT_MENU },
|
|
|
+ });
|
|
|
return child.get(Modula2ExplorerWidget);
|
|
|
}
|