Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support scmResourceState in when clauses #90952

Merged
merged 11 commits into from Aug 9, 2020
25 changes: 25 additions & 0 deletions src/vs/vscode.proposed.d.ts
Expand Up @@ -2022,6 +2022,31 @@ declare module 'vscode' {

//#endregion

//#region Support `scmResourceState` in `when` clauses #86180 https://github.com/microsoft/vscode/issues/86180

export interface SourceControlResourceState {
/**
* Context value of the resource state. This can be used to contribute resource specific actions.
* For example, if a resource is given a context value as `diffable`. When contributing actions to `scm/resourceState/context`
* using `menus` extension point, you can specify context value for key `scmResourceState` in `when` expressions, like `scmResourceState == diffable`.
* ```
* "contributes": {
* "menus": {
* "scm/resourceState/context": [
* {
* "command": "extension.diff",
* "when": "scmResourceState == diffable"
* }
* ]
* }
* }
* ```
* This will show action `extension.diff` only for resources with `contextValue` is `diffable`.
*/
readonly contextValue?: string;
}

//#endregion
//#region https://github.com/microsoft/vscode/issues/101857

export interface ExtensionContext {
Expand Down
8 changes: 5 additions & 3 deletions src/vs/workbench/api/browser/mainThreadSCM.ts
Expand Up @@ -68,7 +68,8 @@ class MainThreadSCMResource implements ISCMResource {
private readonly handle: number,
public sourceUri: URI,
public resourceGroup: ISCMResourceGroup,
public decorations: ISCMResourceDecorations
public decorations: ISCMResourceDecorations,
public contextValue: string
) { }

open(preserveFocus: boolean): Promise<void> {
Expand Down Expand Up @@ -198,7 +199,7 @@ class MainThreadSCMProvider implements ISCMProvider {

for (const [start, deleteCount, rawResources] of groupSlices) {
const resources = rawResources.map(rawResource => {
const [handle, sourceUri, icons, tooltip, strikeThrough, faded] = rawResource;
const [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const decorations = {
Expand All @@ -216,7 +217,8 @@ class MainThreadSCMProvider implements ISCMProvider {
handle,
URI.revive(sourceUri),
group,
decorations
decorations,
contextValue
);
});

Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Expand Up @@ -818,7 +818,8 @@ export type SCMRawResource = [
UriComponents[] /*icons: light, dark*/,
string /*tooltip*/,
boolean /*strike through*/,
boolean /*faded*/
boolean /*faded*/,
string /*context value*/
];

export type SCMRawResourceSplice = [
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/common/extHostSCM.ts
Expand Up @@ -311,8 +311,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
const tooltip = (r.decorations && r.decorations.tooltip) || '';
const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
const faded = r.decorations && !!r.decorations.faded;
const contextValue = r.contextValue || '';

const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded] as SCMRawResource;
const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue] as SCMRawResource;

return { rawResource, handle };
});
Expand Down
41 changes: 35 additions & 6 deletions src/vs/workbench/contrib/scm/browser/menus.ts
Expand Up @@ -30,8 +30,12 @@ interface ISCMResourceGroupMenuEntry {

interface ISCMMenus {
readonly resourceGroupMenu: IMenu;
readonly resourceMenu: IMenu;
readonly resourceFolderMenu: IMenu;
readonly resourceMenusByContext: Map<string, ISCMResourceMenu>;
}

interface ISCMResourceMenu extends IDisposable {
readonly menu: IMenu;
}

export function getSCMResourceContextKey(resource: ISCMResourceGroup | ISCMResource): string {
Expand Down Expand Up @@ -148,6 +152,9 @@ export class SCMRepositoryMenus implements IDisposable {
private getActions(menuId: MenuId, resource: ISCMResourceGroup | ISCMResource): { primary: IAction[]; secondary: IAction[]; } {
const contextKeyService = this.contextKeyService.createScoped();
contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource));
if (isSCMResource(resource)) {
contextKeyService.createKey('scmResourceState', resource.contextValue);
}

const menu = this.menuService.createMenu(menuId, contextKeyService);
const primary: IAction[] = [];
Expand All @@ -160,6 +167,20 @@ export class SCMRepositoryMenus implements IDisposable {
return result;
}

private createResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): ISCMResourceMenu {
const contextKeyService = this.contextKeyService.createScoped();
contextKeyService.createKey('scmProvider', group.provider.contextValue);
contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource));
contextKeyService.createKey('scmResourceState', resource.contextValue);

const menu = this.menuService.createMenu(MenuId.SCMResourceContext, contextKeyService);

const disposable = combinedDisposable(menu, contextKeyService);
const dispose = () => disposable.dispose();

return { menu, dispose };
}

getResourceGroupMenu(group: ISCMResourceGroup): IMenu {
if (!this.resourceGroupMenus.has(group)) {
throw new Error('SCM Resource Group menu not found');
Expand All @@ -168,12 +189,17 @@ export class SCMRepositoryMenus implements IDisposable {
return this.resourceGroupMenus.get(group)!.resourceGroupMenu;
}

getResourceMenu(group: ISCMResourceGroup): IMenu {
getResourceMenu(group: ISCMResourceGroup, resource: ISCMResource): IMenu {
if (!this.resourceGroupMenus.has(group)) {
throw new Error('SCM Resource Group menu not found');
}

return this.resourceGroupMenus.get(group)!.resourceMenu;
const foundGroup = this.resourceGroupMenus.get(group)!;
if (!foundGroup.resourceMenusByContext.has(resource.contextValue)) {
foundGroup.resourceMenusByContext.set(resource.contextValue, this.createResourceMenu(group, resource));
}

return foundGroup.resourceMenusByContext.get(resource.contextValue)!.menu;
}

getResourceFolderMenu(group: ISCMResourceGroup): IMenu {
Expand All @@ -191,11 +217,14 @@ export class SCMRepositoryMenus implements IDisposable {
contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(group));

const resourceGroupMenu = this.menuService.createMenu(MenuId.SCMResourceGroupContext, contextKeyService);
const resourceMenu = this.menuService.createMenu(MenuId.SCMResourceContext, contextKeyService);
const resourceFolderMenu = this.menuService.createMenu(MenuId.SCMResourceFolderContext, contextKeyService);
const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceMenu, resourceFolderMenu);

this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceMenu, resourceFolderMenu });
const resourceMenusByContext = new Map<string, ISCMResourceMenu>();
const resourceMenusDisposable = { dispose: () => resourceMenusByContext.forEach(menu => menu.dispose()) };

const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceFolderMenu, resourceMenusDisposable);

this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceFolderMenu, resourceMenusByContext });
return { group, disposable };
});

Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/scm/browser/scmViewPane.ts
Expand Up @@ -511,7 +511,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
if (ResourceTree.isResourceNode(resourceOrFolder)) {
if (resourceOrFolder.element) {
const menus = this.menus.getRepositoryMenus(resourceOrFolder.element.resourceGroup.provider);
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceMenu(resourceOrFolder.element.resourceGroup), template.actionBar));
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceMenu(resourceOrFolder.element.resourceGroup, resourceOrFolder.element), template.actionBar));
toggleClass(template.name, 'strike-through', resourceOrFolder.element.decorations.strikeThrough);
toggleClass(template.element, 'faded', resourceOrFolder.element.decorations.faded);
} else {
Expand All @@ -522,7 +522,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
}
} else {
const menus = this.menus.getRepositoryMenus(resourceOrFolder.resourceGroup.provider);
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceMenu(resourceOrFolder.resourceGroup), template.actionBar));
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceMenu(resourceOrFolder.resourceGroup, resourceOrFolder), template.actionBar));
toggleClass(template.name, 'strike-through', resourceOrFolder.decorations.strikeThrough);
toggleClass(template.element, 'faded', resourceOrFolder.decorations.faded);
}
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/scm/common/scm.ts
Expand Up @@ -31,6 +31,7 @@ export interface ISCMResource {
readonly resourceGroup: ISCMResourceGroup;
readonly sourceUri: URI;
readonly decorations: ISCMResourceDecorations;
readonly contextValue: string;
open(preserveFocus: boolean): Promise<void>;
}

Expand Down