actions.ts 5.8 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import { Registry } from 'vs/platform/registry/common/platform';
M
Matt Bierner 已提交
7 8
import { IAction } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
9
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
M
Matt Bierner 已提交
10
import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
E
Erich Gamma 已提交
11 12 13 14 15 16 17 18 19

/**
 * The action bar contributor allows to add actions to an actionbar in a given context.
 */
export class ActionBarContributor {

	/**
	 * Returns true if this contributor has actions for the given context.
	 */
20
	hasActions(context: unknown): boolean {
E
Erich Gamma 已提交
21 22 23 24 25 26
		return false;
	}

	/**
	 * Returns an array of primary actions in the given context.
	 */
27
	getActions(context: unknown): ReadonlyArray<IAction> {
E
Erich Gamma 已提交
28 29 30 31 32 33 34 35
		return [];
	}
}

/**
 * Some predefined scopes to contribute actions to
 */
export const Scope = {
36

37 38 39
	/**
	 * Actions inside tree widgets.
	 */
E
Erich Gamma 已提交
40 41 42 43 44 45 46
	VIEWER: 'viewer'
};

/**
 * The ContributableActionProvider leverages the actionbar contribution model to find actions.
 */
export class ContributableActionProvider implements IActionProvider {
47
	private readonly registry: IActionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
E
Erich Gamma 已提交
48

49
	private toContext(tree: ITree, element: unknown): unknown {
E
Erich Gamma 已提交
50 51 52 53 54 55
		return {
			viewer: tree,
			element: element
		};
	}

56
	hasActions(tree: ITree, element: unknown): boolean {
B
Benjamin Pasero 已提交
57
		const context = this.toContext(tree, element);
E
Erich Gamma 已提交
58

B
Benjamin Pasero 已提交
59
		const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
60
		return contributors.some(contributor => contributor.hasActions(context));
E
Erich Gamma 已提交
61 62
	}

63
	getActions(tree: ITree, element: unknown): ReadonlyArray<IAction> {
B
Benjamin Pasero 已提交
64 65
		const actions: IAction[] = [];
		const context = this.toContext(tree, element);
E
Erich Gamma 已提交
66 67

		// Collect Actions
B
Benjamin Pasero 已提交
68
		const contributors = this.registry.getActionBarContributors(Scope.VIEWER);
69
		for (const contributor of contributors) {
E
Erich Gamma 已提交
70 71 72 73 74
			if (contributor.hasActions(context)) {
				actions.push(...contributor.getActions(context));
			}
		}

75
		return prepareActions(actions);
E
Erich Gamma 已提交
76 77 78 79 80
	}
}

// Helper function used in parts to massage actions before showing in action areas
export function prepareActions(actions: IAction[]): IAction[] {
B
Benjamin Pasero 已提交
81 82 83
	if (!actions.length) {
		return actions;
	}
E
Erich Gamma 已提交
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

	// Clean up leading separators
	let firstIndexOfAction = -1;
	for (let i = 0; i < actions.length; i++) {
		if (actions[i].id === Separator.ID) {
			continue;
		}

		firstIndexOfAction = i;
		break;
	}

	if (firstIndexOfAction === -1) {
		return [];
	}

	actions = actions.slice(firstIndexOfAction);

	// Clean up trailing separators
	for (let h = actions.length - 1; h >= 0; h--) {
B
Benjamin Pasero 已提交
104
		const isSeparator = actions[h].id === Separator.ID;
E
Erich Gamma 已提交
105 106 107 108 109 110 111 112 113 114
		if (isSeparator) {
			actions.splice(h, 1);
		} else {
			break;
		}
	}

	// Clean up separator duplicates
	let foundAction = false;
	for (let k = actions.length - 1; k >= 0; k--) {
B
Benjamin Pasero 已提交
115
		const isSeparator = actions[k].id === Separator.ID;
E
Erich Gamma 已提交
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
		if (isSeparator && !foundAction) {
			actions.splice(k, 1);
		} else if (!isSeparator) {
			foundAction = true;
		} else if (isSeparator) {
			foundAction = false;
		}
	}

	return actions;
}

export const Extensions = {
	Actionbar: 'workbench.contributions.actionbar'
};

export interface IActionBarRegistry {
	/**
	 * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars
B
Benjamin Pasero 已提交
135
	 * that are used in the Workbench in the given scope.
E
Erich Gamma 已提交
136
	 */
M
Matt Bierner 已提交
137
	registerActionBarContributor<Services extends BrandedService[]>(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void;
E
Erich Gamma 已提交
138 139 140 141 142 143

	/**
	 * Returns an array of registered action bar contributors known to the workbench for the given scope.
	 */
	getActionBarContributors(scope: string): ActionBarContributor[];

B
Benjamin Pasero 已提交
144 145 146 147
	/**
	 * Starts the registry by providing the required services.
	 */
	start(accessor: ServicesAccessor): void;
E
Erich Gamma 已提交
148 149 150
}

class ActionBarRegistry implements IActionBarRegistry {
151
	private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0<ActionBarContributor>; }[] = [];
152
	private readonly actionBarContributorInstances: Map<string, ActionBarContributor[]> = new Map();
153
	private instantiationService: IInstantiationService | undefined;
E
Erich Gamma 已提交
154

B
Benjamin Pasero 已提交
155 156
	start(accessor: ServicesAccessor): void {
		this.instantiationService = accessor.get(IInstantiationService);
E
Erich Gamma 已提交
157 158

		while (this.actionBarContributorConstructors.length > 0) {
M
Matt Bierner 已提交
159
			const entry = this.actionBarContributorConstructors.shift()!;
E
Erich Gamma 已提交
160 161 162 163
			this.createActionBarContributor(entry.scope, entry.ctor);
		}
	}

164
	private createActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
165 166 167 168 169 170 171 172
		if (this.instantiationService) {
			const instance = this.instantiationService.createInstance(ctor);
			let target = this.actionBarContributorInstances.get(scope);
			if (!target) {
				target = [];
				this.actionBarContributorInstances.set(scope, target);
			}
			target.push(instance);
173
		}
E
Erich Gamma 已提交
174 175 176
	}

	private getContributors(scope: string): ActionBarContributor[] {
177
		return this.actionBarContributorInstances.get(scope) || [];
E
Erich Gamma 已提交
178 179
	}

B
Benjamin Pasero 已提交
180
	registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void {
E
Erich Gamma 已提交
181 182 183 184 185 186 187 188 189 190
		if (!this.instantiationService) {
			this.actionBarContributorConstructors.push({
				scope: scope,
				ctor: ctor
			});
		} else {
			this.createActionBarContributor(scope, ctor);
		}
	}

B
Benjamin Pasero 已提交
191
	getActionBarContributors(scope: string): ActionBarContributor[] {
E
Erich Gamma 已提交
192 193 194 195
		return this.getContributors(scope).slice(0);
	}
}

196
Registry.add(Extensions.Actionbar, new ActionBarRegistry());