Skip to content
Merged
25 changes: 25 additions & 0 deletions src/vs/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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