Skip to content

Commit

Permalink
Introduce share links in more places via a contrib (#177311)
Browse files Browse the repository at this point in the history
* Introduce share links in more places via a contrib

* Update `build/lib/i18n.resources.json`
  • Loading branch information
joyceerhl authored Mar 16, 2023
1 parent c97d581 commit e9ff97a
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 77 deletions.
4 changes: 4 additions & 0 deletions build/lib/i18n.resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@
"name": "vs/workbench/contrib/searchEditor",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/share",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/snippets",
"project": "vscode-workbench"
Expand Down
47 changes: 6 additions & 41 deletions extensions/github/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,13 @@
},
{
"command": "github.copyVscodeDevLink",
"enablement": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'",
"title": "Copy vscode.dev Link"
},
{
"command": "github.copyVscodeDevLinkFile",
"title": "Copy vscode.dev Link"
},
{
"command": "github.copyVscodeDevLinkWithoutRange",
"title": "Copy vscode.dev Link"
},
{
"command": "github.openOnVscodeDev",
"title": "Open in vscode.dev",
Expand Down Expand Up @@ -77,56 +74,24 @@
"command": "github.copyVscodeDevLinkFile",
"when": "false"
},
{
"command": "github.copyVscodeDevLinkWithoutRange",
"when": "false"
},
{
"command": "github.openOnVscodeDev",
"when": "false"
}
],
"file/share": [
{
"command": "github.copyVscodeDevLinkFile",
"when": "github.hasGitHubRepo && remoteName != 'codespaces'",
"group": "0_vscode@0"
}
],
"editor/context/share": [
{
"command": "github.copyVscodeDevLink",
"when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'",
"group": "0_vscode@0"
}
],
"explorer/context/share": [
{
"command": "github.copyVscodeDevLinkWithoutRange",
"when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'",
"group": "0_vscode@0"
}
],
"editor/lineNumber/context": [
{
"command": "github.copyVscodeDevLink",
"when": "github.hasGitHubRepo && resourceScheme != untitled && activeEditor == workbench.editors.files.textFileEditor && config.editor.lineNumbers == on && remoteName != 'codespaces'",
"group": "1_cutcopypaste@2"
},
"share": [
{
"command": "github.copyVscodeDevLink",
"when": "github.hasGitHubRepo && resourceScheme != untitled && activeEditor == workbench.editor.notebook && remoteName != 'codespaces'",
"group": "1_cutcopypaste@2"
"title": "Copy vscode.dev Link"
}
],
"editor/title/context/share": [
"file/share": [
{
"command": "github.copyVscodeDevLinkWithoutRange",
"when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'",
"command": "github.copyVscodeDevLinkFile",
"when": "github.hasGitHubRepo && remoteName != 'codespaces'",
"group": "0_vscode@0"
}
]

},
"configuration": [
{
Expand Down
9 changes: 4 additions & 5 deletions extensions/github/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,17 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
}
}));

disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async (context: LinkContext) => {
disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async (context: LinkContext, ranges?: vscode.Range[]) => {
if (Array.isArray(ranges) && ranges.every((range) => 'start' in range && 'end' in range) && context instanceof vscode.Uri) {
context = { uri: context, ranges };
}
return copyVscodeDevLink(gitAPI, true, context);
}));

disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async (context: LinkContext) => {
return copyVscodeDevLink(gitAPI, false, context);
}));

disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkWithoutRange', async (context: LinkContext) => {
return copyVscodeDevLink(gitAPI, true, context, false);
}));

disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => {
return openVscodeDevLink(gitAPI);
}));
Expand Down
48 changes: 27 additions & 21 deletions extensions/github/src/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,41 @@ interface INotebookPosition {
range: vscode.Range | undefined;
}

interface EditorContext {
uri: vscode.Uri;
ranges: vscode.Range[];
}

interface EditorLineNumberContext {
uri: vscode.Uri;
lineNumber: number;
}
export type LinkContext = vscode.Uri | EditorLineNumberContext | undefined;

function extractContext(context: LinkContext): { fileUri: vscode.Uri | undefined; lineNumber: number | undefined } {
interface ScmResourceContext {
resourceUri: vscode.Uri;
}

export type LinkContext = vscode.Uri | EditorContext | EditorLineNumberContext | undefined | ScmResourceContext;

function extractContext(context: LinkContext): { fileUri: vscode.Uri | undefined; ranges: vscode.Range[] | undefined } {
if (context instanceof vscode.Uri) {
return { fileUri: context, lineNumber: undefined };
return { fileUri: context, ranges: undefined };
} else if (context !== undefined && 'ranges' in context && 'uri' in context) {
return { fileUri: context.uri, ranges: context.ranges };
} else if (context !== undefined && 'lineNumber' in context && 'uri' in context) {
return { fileUri: context.uri, lineNumber: context.lineNumber };
return { fileUri: context.uri, ranges: [new vscode.Range(context.lineNumber - 1, 0, context.lineNumber - 1, 1)] };
} else if (context !== undefined && 'resourceUri' in context) {
return { fileUri: context.resourceUri, ranges: undefined };
} else {
return { fileUri: undefined, lineNumber: undefined };
return { fileUri: undefined, ranges: undefined };
}
}

function getFileAndPosition(context: LinkContext): IFilePosition | INotebookPosition | undefined {
let range: vscode.Range | undefined;

const { fileUri, lineNumber } = extractContext(context);
const uri = fileUri ?? vscode.window.activeTextEditor?.document.uri;
const { fileUri, ranges: selections } = extractContext(context);
const uri = fileUri;

if (uri) {
if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) {
Expand All @@ -69,11 +83,11 @@ function getFileAndPosition(context: LinkContext): IFilePosition | INotebookPosi
const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment);
const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start;

const range = getRangeOrSelection(lineNumber);
const range = selections?.[0];
return { type: LinkType.Notebook, uri, cellIndex, range };
} else {
// the active editor is a text editor
range = getRangeOrSelection(lineNumber);
range = selections?.[0];
return { type: LinkType.File, uri, range };
}
}
Expand All @@ -86,12 +100,6 @@ function getFileAndPosition(context: LinkContext): IFilePosition | INotebookPosi
return undefined;
}

function getRangeOrSelection(lineNumber: number | undefined) {
return lineNumber !== undefined && (!vscode.window.activeTextEditor || vscode.window.activeTextEditor.selection.isEmpty || !vscode.window.activeTextEditor.selection.contains(new vscode.Position(lineNumber - 1, 0)))
? new vscode.Range(lineNumber - 1, 0, lineNumber - 1, 1)
: vscode.window.activeTextEditor?.selection;
}

function rangeString(range: vscode.Range | undefined) {
if (!range) {
return '';
Expand Down Expand Up @@ -122,10 +130,7 @@ export function notebookCellRangeString(index: number | undefined, range: vscode
export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): string | undefined {
hostPrefix = hostPrefix ?? 'https://github.com';
const fileAndPosition = getFileAndPosition(context);
if (!fileAndPosition) {
return;
}
const uri = fileAndPosition.uri;
const uri = fileAndPosition?.uri;

// Use the first repo if we cannot determine a repo from the uri.
const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0];
Expand All @@ -150,9 +155,10 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri
}

const blobSegment = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${linkType === 'headlink' ? gitRepo.state.HEAD.name : gitRepo.state.HEAD?.commit}` : '';
const fileSegments = fileAndPosition.type === LinkType.File
const fileSegments = fileAndPosition && uri ? (fileAndPosition.type === LinkType.File
? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${useRange ? rangeString(fileAndPosition.range) : ''}` : '')
: (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${useRange ? notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range) : ''}` : '');
: (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${useRange ? notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range) : ''}` : ''))
: '';

return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment
}${fileSegments}`;
Expand Down
4 changes: 0 additions & 4 deletions src/vs/editor/contrib/clipboard/browser/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import * as nls from 'vs/nls';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';

Expand Down Expand Up @@ -108,9 +107,6 @@ export const CopyAction = supportsCopy ? registerCommand(new MultiCommand({

MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { submenu: MenuId.MenubarCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: '2_ccp', order: 3 });
MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextCopy, title: { value: nls.localize('copy as', "Copy As"), original: 'Copy As', }, group: CLIPBOARD_CONTEXT_MENU_GROUP, order: 3 });
MenuRegistry.appendMenuItem(MenuId.EditorContext, { submenu: MenuId.EditorContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1, when: ContextKeyExpr.and(ContextKeyExpr.notEquals('resourceScheme', 'output'), EditorContextKeys.editorTextFocus) });
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { submenu: MenuId.EditorTitleContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1 });
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { submenu: MenuId.ExplorerContextShare, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1 });

export const PasteAction = supportsPaste ? registerCommand(new MultiCommand({
id: 'editor.action.clipboardPasteAction',
Expand Down
13 changes: 12 additions & 1 deletion src/vs/editor/contrib/contextmenu/browser/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,18 @@ export class ContextMenuController implements IEditorContribution {

// get menu groups
const menu = this._menuService.createMenu(menuId, this._contextKeyService);
const groups = menu.getActions({ arg: model.uri });
const groups = menu.getActions({
arg: [model.uri, this._editor.getSelections()?.map((selection) => ({
start: {
line: selection.getStartPosition().lineNumber - 1,
character: selection.getStartPosition().column
},
end: {
line: selection.getEndPosition().lineNumber - 1,
character: selection.getEndPosition().column
}
}))]
});
menu.dispose();

// translate them into other actions
Expand Down
7 changes: 6 additions & 1 deletion src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,18 @@ export class MenuId {
static readonly MenubarViewMenu = new MenuId('MenubarViewMenu');
static readonly MenubarHomeMenu = new MenuId('MenubarHomeMenu');
static readonly OpenEditorsContext = new MenuId('OpenEditorsContext');
static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare');
static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext');
static readonly SCMChangeContext = new MenuId('SCMChangeContext');
static readonly SCMResourceContext = new MenuId('SCMResourceContext');
static readonly SCMResourceContextShare = new MenuId('SCMResourceContextShare');
static readonly SCMResourceFolderContext = new MenuId('SCMResourceFolderContext');
static readonly SCMResourceGroupContext = new MenuId('SCMResourceGroupContext');
static readonly SCMSourceControl = new MenuId('SCMSourceControl');
static readonly SCMTitle = new MenuId('SCMTitle');
static readonly SearchContext = new MenuId('SearchContext');
static readonly SearchActionMenu = new MenuId('SearchActionContext');
static readonly Share = new MenuId('Share');
static readonly StatusBarWindowIndicatorMenu = new MenuId('StatusBarWindowIndicatorMenu');
static readonly StatusBarRemoteIndicatorMenu = new MenuId('StatusBarRemoteIndicatorMenu');
static readonly StickyScrollContext = new MenuId('StickyScrollContext');
Expand Down Expand Up @@ -477,7 +480,9 @@ export class MenuItemAction implements IAction {
run(...args: any[]): Promise<void> {
let runArgs: any[] = [];

if (this._options?.arg) {
if (this._options?.arg && Array.isArray(this._options.arg)) {
runArgs = [...runArgs, ...this._options.arg];
} else if (this._options?.arg) {
runArgs = [...runArgs, this._options.arg];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,6 @@ export class EditorLineNumberContextMenu extends Disposable implements IEditorCo
actions.push(collectedActions);
}

const menuActions = menu.getActions({ arg: { lineNumber, uri: model.uri }, shouldForwardArgs: true });
actions.push(...menuActions.map(a => a[1]));

// if the current editor selections do not contain the target line number,
// set the selection to the clicked line number
if (e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) {
Expand All @@ -107,11 +104,24 @@ export class EditorLineNumberContextMenu extends Disposable implements IEditorCo
}
}

const ranges = this.editor.getSelections()?.map((selection) => ({
start: {
line: selection.getStartPosition().lineNumber - 1,
character: selection.getStartPosition().column
},
end: {
line: selection.getEndPosition().lineNumber - 1,
character: selection.getEndPosition().column
}
}));
const menuActions = menu.getActions({ arg: { lineNumber, uri: model.uri, ranges } });
actions.push(...menuActions.map(a => a[1]));

this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => Separator.join(...actions),
menuActionOptions: { shouldForwardArgs: true },
getActionsContext: () => ({ lineNumber, uri: model.uri }),
getActionsContext: () => ({ lineNumber, uri: model.uri, ranges }),
onHide: () => menu.dispose(),
});
});
Expand Down
64 changes: 64 additions & 0 deletions src/vs/workbench/contrib/share/browser/share.contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { DisposableStore } from 'vs/base/common/lifecycle';
import { IMenuService, MenuId, MenuRegistry, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import * as nls from 'vs/nls';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';

const menuIds: [MenuId, MenuId | undefined, ContextKeyExpression | undefined][] = [
[MenuId.EditorContext, MenuId.EditorContextShare, ContextKeyExpr.and(ContextKeyExpr.notEquals('resourceScheme', 'output'), EditorContextKeys.editorTextFocus)],
[MenuId.EditorTitleContext, MenuId.EditorTitleContextShare, undefined],
[MenuId.ExplorerContext, MenuId.ExplorerContextShare, undefined],
[MenuId.OpenEditorsContext, MenuId.OpenEditorsContextShare, undefined],
[MenuId.SCMResourceContext, MenuId.SCMResourceContextShare, undefined],
[MenuId.EditorLineNumberContext, undefined, undefined],
];

for (const [menuId, submenuId, when] of menuIds) {
if (submenuId !== undefined) {
MenuRegistry.appendMenuItem(menuId, { submenu: submenuId, title: { value: nls.localize('share', "Share"), original: 'Share', }, group: '11_share', order: -1, when });
}
}

class ShareContribution {

private readonly disposableStore = new DisposableStore();
private readonly menu = this.menuService.createMenu(MenuId.Share, this.contextKeyService);

constructor(
@IMenuService private readonly menuService: IMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
this.ensureActions();
this.menu.onDidChange(() => this.ensureActions());
}

private ensureActions(): void {
this.disposableStore.clear();

const allActions = this.menu.getActions();

for (const [_, actions] of allActions) {
for (const action of actions) {
if (action instanceof SubmenuItemAction || !action.enabled) {
continue;
}
for (const [menuId, submenuId] of menuIds) {
this.disposableStore.add(MenuRegistry.appendMenuItem(
submenuId ?? menuId,
{ command: action.item }
));
}
}
}
}
}

Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(ShareContribution, LifecyclePhase.Eventually);
Loading

0 comments on commit e9ff97a

Please sign in to comment.