import { commands, Disposable, OutputChannel, window, workspace } from 'vscode';
import { Pijul } from './pijul';
import { Repository } from './repository';
import { dispose } from './utils/disposableUtils';
/**
* Interface representing the command creation options,
* which determines which parameters are passed from the
* command centre to the command.
*/
interface ICommandOptions {
repository?: boolean
// diff?: boolean
}
/**
* Interface that describes the fields required to register
* a new command with VS Code.
*/
interface ICommand {
/** The command ID that the command will be registered under, matching the one in `package.json` */
commandId: string
/** The name of the function that the command invokes */
key: string
/** The function that will be invoked by the command */
method: Function
/** The options for the command creation */
options: ICommandOptions
}
/**
* An array which holds all of the commands which will
* be registered by the command centre
*/
const Commands: ICommand[] = [];
/**
* Decorator function for adding functions to the Commands array
* for registration.
* @param commandId The id that will be assigned to the command, matching the one in `package.json`
* @param options The command options which control how it is created
*/
function command (commandId: string, options: ICommandOptions = {}): Function {
return (_target: any, key: string, descriptor: any) => {
if (!(typeof descriptor.value === 'function')) {
throw new Error('not supported');
}
Commands.push({ commandId, key, method: descriptor.value, options });
};
}
export class CommandCentre {
private disposables: Disposable[];
constructor (
private readonly pijul: Pijul,
private readonly repository: Repository,
private readonly outputChannel: OutputChannel
) {
this.disposables = Commands.map(({ commandId, key, method, options }) => {
const command = this.createCommand(key, method, options);
return commands.registerCommand(commandId, command);
});
this.outputChannel.appendLine('Activated Command Centre');
}
/**
* Wraps a specific command function in a generic command function for error
* and argument handling.
* @param key The name of the function that the command invokes
* @param func The actual function which will be wrapped in the generic one
* @param options The options which determine which Command centre fields will be passed to the command
*/
private createCommand (key: string, func: Function, options: ICommandOptions): (...args: any[]) => any {
const result = async (...args: any[]): Promise<any> => {
let result: Promise<any>;
if (!options.repository) {
result = Promise.resolve(func.apply(this, args));
} else {
// TODO: Handle multiple open repositories
return await Promise.resolve(func.apply(this, [this.repository, ...args]));
}
// TODO: Generic command-level error handling
return await result;
};
// Update the object so that directly invoking the command function on the command centre calls the wrapped version.
(this as any)[key] = result;
return result;
}
/**
* Initialize a new Pijul repository in the current working directory.
*/
@command('pijul.init')
async init (): Promise<void> {
// TODO: Select workspace folder to init in
const cwd = workspace.workspaceFolders?.[0]?.uri.path;
if (cwd) {
await this.pijul.init(cwd);
this.refresh(this.repository);
} else {
await window.showErrorMessage('No workspace folders open, cannot initialize Pijul repository');
}
}
/**
* Refresh the the state of the files within the extension.
* @param repository The repository to run the file in
*/
@command('pijul.refresh', { repository: true })
async refresh (repository: Repository): Promise<void> {
await repository.refreshStatus();
}
dispose (): void {
this.disposables = dispose(this.disposables);
}
}