mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
Initial share provider API and UI (#182999)
* Formalize share provider API * i18n.resources.json * Don't introduce a generic Success dialog severity
This commit is contained in:
parent
d470f53f49
commit
bc1090cc10
|
@ -510,6 +510,10 @@
|
|||
"name": "vs/workbench/services/localization",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/share",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/contrib/accessibility",
|
||||
"project": "vscode-workbench"
|
||||
|
|
|
@ -457,6 +457,7 @@
|
|||
"--vscode-problemsErrorIcon-foreground",
|
||||
"--vscode-problemsInfoIcon-foreground",
|
||||
"--vscode-problemsWarningIcon-foreground",
|
||||
"--vscode-problemsSuccessIcon-foreground",
|
||||
"--vscode-profileBadge-background",
|
||||
"--vscode-profileBadge-foreground",
|
||||
"--vscode-progressBar-background",
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
"enabledApiProposals": [
|
||||
"contribShareMenu",
|
||||
"contribEditSessions",
|
||||
"canonicalUriProvider"
|
||||
"canonicalUriProvider",
|
||||
"shareProvider"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
|
|
|
@ -14,6 +14,7 @@ import { GitBaseExtension } from './typings/git-base';
|
|||
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
||||
import { GithubBranchProtectionProviderManager } from './branchProtection';
|
||||
import { GitHubCanonicalUriProvider } from './canonicalUriProvider';
|
||||
import { VscodeDevShareProvider } from './shareProviders';
|
||||
|
||||
export function activate(context: ExtensionContext): void {
|
||||
const disposables: Disposable[] = [];
|
||||
|
@ -95,6 +96,7 @@ function initializeGitExtension(context: ExtensionContext, logger: LogOutputChan
|
|||
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
|
||||
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
||||
disposables.add(new GitHubCanonicalUriProvider(gitAPI));
|
||||
disposables.add(new VscodeDevShareProvider(gitAPI));
|
||||
setGitHubContext(gitAPI, disposables);
|
||||
|
||||
commands.executeCommand('setContext', 'git-base.gitEnabled', true);
|
||||
|
|
|
@ -92,7 +92,7 @@ function getRangeOrSelection(lineNumber: number | undefined) {
|
|||
: vscode.window.activeTextEditor?.selection;
|
||||
}
|
||||
|
||||
function rangeString(range: vscode.Range | undefined) {
|
||||
export function rangeString(range: vscode.Range | undefined) {
|
||||
if (!range) {
|
||||
return '';
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ export function notebookCellRangeString(index: number | undefined, range: vscode
|
|||
return hash;
|
||||
}
|
||||
|
||||
function encodeURIComponentExceptSlashes(path: string) {
|
||||
export function encodeURIComponentExceptSlashes(path: string) {
|
||||
// There may be special characters like # and whitespace in the path.
|
||||
// These characters are not escaped by encodeURI(), so it is not sufficient to
|
||||
// feed the full URI to encodeURI().
|
||||
|
|
111
extensions/github/src/shareProviders.ts
Normal file
111
extensions/github/src/shareProviders.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { API } from './typings/git';
|
||||
import { getRepositoryFromUrl, repositoryHasGitHubRemote } from './util';
|
||||
import { encodeURIComponentExceptSlashes, getRepositoryForFile, notebookCellRangeString, rangeString } from './links';
|
||||
|
||||
export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disposable {
|
||||
readonly id: string = 'copyVscodeDevLink';
|
||||
readonly label: string = vscode.l10n.t('Copy vscode.dev Link');
|
||||
readonly priority: number = 10;
|
||||
|
||||
|
||||
private _hasGitHubRepositories: boolean = false;
|
||||
private set hasGitHubRepositories(value: boolean) {
|
||||
vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', value);
|
||||
this._hasGitHubRepositories = value;
|
||||
this.ensureShareProviderRegistration();
|
||||
}
|
||||
|
||||
private shareProviderRegistration: vscode.Disposable | undefined;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(private readonly gitAPI: API) {
|
||||
this.initializeGitHubRepoContext();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
|
||||
private initializeGitHubRepoContext() {
|
||||
if (this.gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) {
|
||||
this.hasGitHubRepositories = true;
|
||||
vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
} else {
|
||||
this.disposables.push(this.gitAPI.onDidOpenRepository(async e => {
|
||||
await e.status();
|
||||
if (repositoryHasGitHubRemote(e)) {
|
||||
vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
this.hasGitHubRepositories = true;
|
||||
}
|
||||
}));
|
||||
}
|
||||
this.disposables.push(this.gitAPI.onDidCloseRepository(() => {
|
||||
if (!this.gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) {
|
||||
this.hasGitHubRepositories = false;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private ensureShareProviderRegistration() {
|
||||
if (vscode.env.appHost !== 'codespaces' && !this.shareProviderRegistration && this._hasGitHubRepositories) {
|
||||
const shareProviderRegistration = vscode.window.registerShareProvider({ scheme: 'file' }, this);
|
||||
this.shareProviderRegistration = shareProviderRegistration;
|
||||
this.disposables.push(shareProviderRegistration);
|
||||
} else if (this.shareProviderRegistration && !this._hasGitHubRepositories) {
|
||||
this.shareProviderRegistration.dispose();
|
||||
this.shareProviderRegistration = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.Uri> {
|
||||
const repository = getRepositoryForFile(this.gitAPI, item.resourceUri);
|
||||
if (!repository) {
|
||||
return;
|
||||
}
|
||||
|
||||
let repo: { owner: string; repo: string } | undefined;
|
||||
repository.state.remotes.find(remote => {
|
||||
if (remote.fetchUrl) {
|
||||
const foundRepo = getRepositoryFromUrl(remote.fetchUrl);
|
||||
if (foundRepo && (remote.name === repository.state.HEAD?.upstream?.remote)) {
|
||||
repo = foundRepo;
|
||||
return;
|
||||
} else if (foundRepo && !repo) {
|
||||
repo = foundRepo;
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
if (!repo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const blobSegment = repository?.state.HEAD?.name ? encodeURIComponentExceptSlashes(repository.state.HEAD?.name) : repository?.state.HEAD?.commit;
|
||||
const filepathSegment = encodeURIComponentExceptSlashes(item.resourceUri.path.substring(repository?.rootUri.path.length));
|
||||
const rangeSegment = getRangeSegment(item);
|
||||
return vscode.Uri.parse(`${this.getVscodeDevHost()}/${repo.owner}/${repo.repo}/blob/${blobSegment}${filepathSegment}${rangeSegment}${rangeSegment}`);
|
||||
|
||||
}
|
||||
|
||||
private getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
}
|
||||
|
||||
function getRangeSegment(item: vscode.ShareableItem) {
|
||||
if (item.resourceUri.scheme === 'vscode-notebook-cell') {
|
||||
const notebookEditor = vscode.window.visibleNotebookEditors.find(editor => editor.notebook.uri.fsPath === item.resourceUri.fsPath);
|
||||
const cell = notebookEditor?.notebook.getCells().find(cell => cell.document.uri.fragment === item.resourceUri?.fragment);
|
||||
const cellIndex = cell?.index ?? notebookEditor?.selection.start;
|
||||
return notebookCellRangeString(cellIndex, item.selection);
|
||||
}
|
||||
|
||||
return rangeString(item.selection);
|
||||
}
|
29
extensions/github/src/typings/vscode.proposed.shareProvider.d.ts
vendored
Normal file
29
extensions/github/src/typings/vscode.proposed.shareProvider.d.ts
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/176316
|
||||
|
||||
declare module 'vscode' {
|
||||
export interface TreeItem {
|
||||
shareableItem?: ShareableItem;
|
||||
}
|
||||
|
||||
export interface ShareableItem {
|
||||
resourceUri: Uri;
|
||||
selection?: Range;
|
||||
}
|
||||
|
||||
export interface ShareProvider {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly priority: number;
|
||||
|
||||
provideShare(item: ShareableItem, token: CancellationToken): ProviderResult<Uri>;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export function registerShareProvider(selector: DocumentSelector, provider: ShareProvider): Disposable;
|
||||
}
|
||||
}
|
|
@ -98,9 +98,11 @@ 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');
|
||||
|
|
|
@ -82,6 +82,7 @@ import './mainThreadAuthentication';
|
|||
import './mainThreadTimeline';
|
||||
import './mainThreadTesting';
|
||||
import './mainThreadSecretState';
|
||||
import './mainThreadShare';
|
||||
import './mainThreadProfilContentHandlers';
|
||||
import './mainThreadSemanticSimilarity';
|
||||
import './mainThreadIssueReporter';
|
||||
|
|
55
src/vs/workbench/api/browser/mainThreadShare.ts
Normal file
55
src/vs/workbench/api/browser/mainThreadShare.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ExtHostContext, ExtHostShareShape, IDocumentFilterDto, MainContext, MainThreadShareShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { IShareProvider, IShareService, IShareableItem } from 'vs/workbench/contrib/share/common/share';
|
||||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadShare)
|
||||
export class MainThreadShare implements MainThreadShareShape {
|
||||
|
||||
private readonly proxy: ExtHostShareShape;
|
||||
private providers = new Map<number, IShareProvider>();
|
||||
private providerDisposables = new Map<number, IDisposable>();
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IShareService private readonly shareService: IShareService
|
||||
) {
|
||||
this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostShare);
|
||||
}
|
||||
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void {
|
||||
const provider: IShareProvider = {
|
||||
id,
|
||||
label,
|
||||
selector,
|
||||
provideShare: async (item: IShareableItem) => {
|
||||
return URI.revive(await this.proxy.$provideShare(handle, item, new CancellationTokenSource().token));
|
||||
}
|
||||
};
|
||||
this.providers.set(handle, provider);
|
||||
const disposable = this.shareService.registerShareProvider(provider);
|
||||
this.providerDisposables.set(handle, disposable);
|
||||
}
|
||||
|
||||
$unregisterShareProvider(handle: number): void {
|
||||
if (this.providers.has(handle)) {
|
||||
this.providers.delete(handle);
|
||||
}
|
||||
if (this.providerDisposables.has(handle)) {
|
||||
this.providerDisposables.delete(handle);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.providers.clear();
|
||||
dispose(this.providerDisposables.values());
|
||||
this.providerDisposables.clear();
|
||||
}
|
||||
}
|
|
@ -103,6 +103,7 @@ import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/
|
|||
import { ExtHostSemanticSimilarity } from 'vs/workbench/api/common/extHostSemanticSimilarity';
|
||||
import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter';
|
||||
import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets';
|
||||
import { ExtHostShare } from 'vs/workbench/api/common/extHostShare';
|
||||
|
||||
export interface IExtensionRegistries {
|
||||
mine: ExtensionDescriptionRegistry;
|
||||
|
@ -186,6 +187,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
|
||||
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService));
|
||||
const extHostQuickDiff = rpcProtocol.set(ExtHostContext.ExtHostQuickDiff, new ExtHostQuickDiff(rpcProtocol, uriTransformer));
|
||||
const extHostShare = rpcProtocol.set(ExtHostContext.ExtHostShare, new ExtHostShare(rpcProtocol, uriTransformer));
|
||||
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, createExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
|
||||
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
|
||||
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol));
|
||||
|
@ -842,6 +844,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
},
|
||||
get tabGroups(): vscode.TabGroups {
|
||||
return extHostEditorTabs.tabGroups;
|
||||
},
|
||||
registerShareProvider(selector: vscode.DocumentSelector, provider: vscode.ShareProvider): vscode.Disposable {
|
||||
checkProposedApiEnabled(extension, 'shareProvider');
|
||||
return extHostShare.registerShareProvider(checkSelector(selector), provider);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -362,6 +362,11 @@ export interface IDocumentFilterDto {
|
|||
isBuiltin?: boolean;
|
||||
}
|
||||
|
||||
export interface IShareableItemDto {
|
||||
resourceUri: UriComponents;
|
||||
range?: IRange;
|
||||
}
|
||||
|
||||
export interface ISignatureHelpProviderMetadataDto {
|
||||
readonly triggerCharacters: readonly string[];
|
||||
readonly retriggerCharacters: readonly string[];
|
||||
|
@ -1247,6 +1252,11 @@ export interface MainThreadSearchShape extends IDisposable {
|
|||
$handleTelemetry(eventName: string, data: any): void;
|
||||
}
|
||||
|
||||
export interface MainThreadShareShape extends IDisposable {
|
||||
$registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void;
|
||||
$unregisterShareProvider(handle: number): void;
|
||||
}
|
||||
|
||||
export interface MainThreadTaskShape extends IDisposable {
|
||||
$createTaskId(task: tasks.ITaskDTO): Promise<string>;
|
||||
$registerTaskProvider(handle: number, type: string): Promise<void>;
|
||||
|
@ -2009,6 +2019,10 @@ export interface ExtHostQuickDiffShape {
|
|||
$provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise<UriComponents | null>;
|
||||
}
|
||||
|
||||
export interface ExtHostShareShape {
|
||||
$provideShare(handle: number, shareableItem: IShareableItemDto, token: CancellationToken): Promise<UriComponents | undefined>;
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
$provideTasks(handle: number, validTypes: { [key: string]: boolean }): Promise<tasks.ITaskSetDTO>;
|
||||
$resolveTask(handle: number, taskDTO: tasks.ITaskDTO): Promise<tasks.ITaskDTO | undefined>;
|
||||
|
@ -2525,6 +2539,7 @@ export const MainContext = {
|
|||
MainThreadExtensionService: createProxyIdentifier<MainThreadExtensionServiceShape>('MainThreadExtensionService'),
|
||||
MainThreadSCM: createProxyIdentifier<MainThreadSCMShape>('MainThreadSCM'),
|
||||
MainThreadSearch: createProxyIdentifier<MainThreadSearchShape>('MainThreadSearch'),
|
||||
MainThreadShare: createProxyIdentifier<MainThreadShareShape>('MainThreadShare'),
|
||||
MainThreadTask: createProxyIdentifier<MainThreadTaskShape>('MainThreadTask'),
|
||||
MainThreadWindow: createProxyIdentifier<MainThreadWindowShape>('MainThreadWindow'),
|
||||
MainThreadLabelService: createProxyIdentifier<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
||||
|
@ -2565,6 +2580,7 @@ export const ExtHostContext = {
|
|||
ExtHostLanguageFeatures: createProxyIdentifier<ExtHostLanguageFeaturesShape>('ExtHostLanguageFeatures'),
|
||||
ExtHostQuickOpen: createProxyIdentifier<ExtHostQuickOpenShape>('ExtHostQuickOpen'),
|
||||
ExtHostQuickDiff: createProxyIdentifier<ExtHostQuickDiffShape>('ExtHostQuickDiff'),
|
||||
ExtHostShare: createProxyIdentifier<ExtHostShareShape>('ExtHostShare'),
|
||||
ExtHostExtensionService: createProxyIdentifier<ExtHostExtensionServiceShape>('ExtHostExtensionService'),
|
||||
ExtHostLogLevelServiceShape: createProxyIdentifier<ExtHostLogLevelServiceShape>('ExtHostLogLevelServiceShape'),
|
||||
ExtHostTerminalService: createProxyIdentifier<ExtHostTerminalServiceShape>('ExtHostTerminalService'),
|
||||
|
|
43
src/vs/workbench/api/common/extHostShare.ts
Normal file
43
src/vs/workbench/api/common/extHostShare.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
import { ExtHostShareShape, IMainContext, IShareableItemDto, MainContext, MainThreadShareShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { DocumentSelector, Range } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { IURITransformer } from 'vs/base/common/uriIpc';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
|
||||
export class ExtHostShare implements ExtHostShareShape {
|
||||
private static handlePool: number = 0;
|
||||
|
||||
private proxy: MainThreadShareShape;
|
||||
private providers: Map<number, vscode.ShareProvider> = new Map();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private readonly uriTransformer: IURITransformer | undefined
|
||||
) {
|
||||
this.proxy = mainContext.getProxy(MainContext.MainThreadShare);
|
||||
}
|
||||
|
||||
async $provideShare(handle: number, shareableItem: IShareableItemDto, token: CancellationToken): Promise<UriComponents | undefined> {
|
||||
const provider = this.providers.get(handle);
|
||||
const result = await provider?.provideShare({ selection: Range.to(shareableItem.range), resourceUri: URI.revive(shareableItem.resourceUri) }, token);
|
||||
return result ?? undefined;
|
||||
}
|
||||
|
||||
registerShareProvider(selector: vscode.DocumentSelector, provider: vscode.ShareProvider): vscode.Disposable {
|
||||
const handle = ExtHostShare.handlePool++;
|
||||
this.providers.set(handle, provider);
|
||||
this.proxy.$registerShareProvider(handle, DocumentSelector.from(selector, this.uriTransformer), provider.id, provider.label);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.proxy.$unregisterShareProvider(handle);
|
||||
this.providers.delete(handle);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -71,6 +71,12 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, {
|
|||
command: revealInOsCommand,
|
||||
when: REVEAL_IN_OS_WHEN_CONTEXT
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.OpenEditorsContextShare, {
|
||||
title: nls.localize('miShare', "Share"),
|
||||
submenu: MenuId.MenubarShare,
|
||||
group: 'share',
|
||||
order: 3,
|
||||
});
|
||||
|
||||
// Menu registration - explorer
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import 'vs/css!./media/scm';
|
|||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { IMenuService, MenuId, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { ISCMResource, ISCMResourceGroup, ISCMProvider, ISCMRepository, ISCMService, ISCMMenus, ISCMRepositoryMenus } from 'vs/workbench/contrib/scm/common/scm';
|
||||
|
@ -15,6 +15,7 @@ import { equals } from 'vs/base/common/arrays';
|
|||
import { ISplice } from 'vs/base/common/sequence';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
function actionEquals(a: IAction, b: IAction): boolean {
|
||||
return a.id === b.id;
|
||||
|
@ -266,3 +267,10 @@ export class SCMMenus implements ISCMMenus, IDisposable {
|
|||
this.disposables.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.SCMResourceContext, {
|
||||
title: localize('miShare', "Share"),
|
||||
submenu: MenuId.SCMResourceContextShare,
|
||||
group: '45_share',
|
||||
order: 3,
|
||||
});
|
||||
|
|
110
src/vs/workbench/contrib/share/browser/share.contribution.ts
Normal file
110
src/vs/workbench/contrib/share/browser/share.contribution.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys';
|
||||
import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { ShareProviderCountContext, ShareService } from 'vs/workbench/contrib/share/browser/shareService';
|
||||
import { IShareService } from 'vs/workbench/contrib/share/common/share';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
const targetMenus = [
|
||||
MenuId.EditorContextShare,
|
||||
MenuId.SCMResourceContextShare,
|
||||
MenuId.OpenEditorsContextShare,
|
||||
MenuId.EditorTitleContextShare,
|
||||
MenuId.MenubarShare,
|
||||
// MenuId.EditorLineNumberContext, // todo@joyceerhl add share
|
||||
MenuId.ExplorerContextShare
|
||||
];
|
||||
|
||||
class ShareWorkbenchContribution {
|
||||
private static SHARE_ENABLED_SETTING = 'workbench.experimental.share.enabled';
|
||||
|
||||
constructor(
|
||||
@IShareService private readonly shareService: IShareService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
if (this.configurationService.getValue<boolean>(ShareWorkbenchContribution.SHARE_ENABLED_SETTING)) {
|
||||
this.registerActions();
|
||||
}
|
||||
}
|
||||
|
||||
private registerActions() {
|
||||
registerAction2(class ShareAction extends Action2 {
|
||||
static readonly ID = 'workbench.action.share';
|
||||
static readonly LABEL = localize('share', 'Share...');
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: ShareAction.ID,
|
||||
title: { value: ShareAction.LABEL, original: 'Share...' },
|
||||
f1: true,
|
||||
icon: Codicon.linkExternal,
|
||||
precondition: ContextKeyExpr.and(ShareProviderCountContext.notEqualsTo(0), WorkspaceFolderCountContext.notEqualsTo(0)),
|
||||
keybinding: {
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.KeyS,
|
||||
},
|
||||
menu: [
|
||||
{ id: MenuId.CommandCenter, order: 1000 }
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
override async run(accessor: ServicesAccessor, ...args: any[]): Promise<void> {
|
||||
const shareService = accessor.get(IShareService);
|
||||
const resourceUri = accessor.get(IWorkspaceContextService).getWorkspace().folders[0].uri;
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
const urlService = accessor.get(IOpenerService);
|
||||
|
||||
const uri = await shareService.provideShare({ resourceUri }, new CancellationTokenSource().token);
|
||||
if (uri) {
|
||||
await clipboardService.writeText(uri.toString());
|
||||
const result = await dialogService.input(
|
||||
{
|
||||
type: Severity.Info,
|
||||
inputs: [{ type: 'text', value: uri.toString() }],
|
||||
message: localize('shareSuccess', 'Copied link to clipboard!'),
|
||||
custom: { icon: Codicon.check },
|
||||
primaryButton: localize('open link', 'Open Link')
|
||||
}
|
||||
);
|
||||
if (result.confirmed) {
|
||||
urlService.open(uri, { openExternal: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const actions = this.shareService.getShareActions();
|
||||
for (const menuId of targetMenus) {
|
||||
for (const action of actions) {
|
||||
// todo@joyceerhl avoid duplicates
|
||||
MenuRegistry.appendMenuItem(menuId, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IShareService, ShareService, InstantiationType.Delayed);
|
||||
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(ShareWorkbenchContribution, LifecyclePhase.Eventually);
|
63
src/vs/workbench/contrib/share/browser/shareService.ts
Normal file
63
src/vs/workbench/contrib/share/browser/shareService.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ISubmenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IShareProvider, IShareService, IShareableItem } from 'vs/workbench/contrib/share/common/share';
|
||||
|
||||
export const ShareProviderCountContext = new RawContextKey<number>('shareProviderCount', 0, localize('shareProviderCount', "The number of available share providers"));
|
||||
|
||||
export class ShareService implements IShareService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly providerCount: IContextKey<number>;
|
||||
private readonly _providers = new Set<IShareProvider>();
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IQuickInputService private quickInputService: IQuickInputService
|
||||
) {
|
||||
this.providerCount = ShareProviderCountContext.bindTo(this.contextKeyService);
|
||||
}
|
||||
|
||||
registerShareProvider(provider: IShareProvider): IDisposable {
|
||||
this._providers.add(provider);
|
||||
this.providerCount.set(this._providers.size);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._providers.delete(provider);
|
||||
this.providerCount.set(this._providers.size);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getShareActions(): ISubmenuItem[] {
|
||||
// todo@joyceerhl return share actions
|
||||
return [];
|
||||
}
|
||||
|
||||
async provideShare(item: IShareableItem, token: CancellationToken): Promise<URI | undefined> {
|
||||
const providers = [...this._providers.values()];
|
||||
|
||||
if (providers.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (providers.length === 1) {
|
||||
return providers[0].provideShare(item, token);
|
||||
}
|
||||
|
||||
const items: (IQuickPickItem & { provider: IShareProvider })[] = providers.map((p) => ({ label: p.label, provider: p }));
|
||||
const selected = await this.quickInputService.pick(items, { canPickMany: false, placeHolder: localize('type to filter', 'Choose how to share {0}', this.labelService.getUriLabel(item.resourceUri)) }, token);
|
||||
return selected?.provider.provideShare(item, token);
|
||||
}
|
||||
}
|
34
src/vs/workbench/contrib/share/common/share.ts
Normal file
34
src/vs/workbench/contrib/share/common/share.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IRange } from 'vs/base/common/range';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { LanguageSelector } from 'vs/editor/common/languageSelector';
|
||||
import { ISubmenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IShareableItem {
|
||||
resourceUri: URI;
|
||||
location?: IRange;
|
||||
}
|
||||
|
||||
export interface IShareProvider {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly selector: LanguageSelector;
|
||||
prepareShare?(item: IShareableItem, token: CancellationToken): Thenable<boolean | undefined>;
|
||||
provideShare(item: IShareableItem, token: CancellationToken): Thenable<URI | undefined>;
|
||||
}
|
||||
|
||||
export const IShareService = createDecorator<IShareService>('shareService');
|
||||
export interface IShareService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
registerShareProvider(provider: IShareProvider): IDisposable;
|
||||
getShareActions(): ISubmenuItem[];
|
||||
provideShare(item: IShareableItem, token: CancellationToken): Thenable<URI | undefined>;
|
||||
}
|
|
@ -73,6 +73,7 @@ export const allApiProposals = Object.freeze({
|
|||
scmTextDocument: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts',
|
||||
scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts',
|
||||
semanticSimilarity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.semanticSimilarity.d.ts',
|
||||
shareProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.shareProvider.d.ts',
|
||||
showLocal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.showLocal.d.ts',
|
||||
tabInputTextMerge: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts',
|
||||
taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts',
|
||||
|
|
|
@ -368,4 +368,7 @@ import 'vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairC
|
|||
// Accessibility
|
||||
import 'vs/workbench/contrib/accessibility/browser/accessibility.contribution';
|
||||
|
||||
// Share
|
||||
import 'vs/workbench/contrib/share/browser/share.contribution';
|
||||
|
||||
//#endregion
|
||||
|
|
29
src/vscode-dts/vscode.proposed.shareProvider.d.ts
vendored
Normal file
29
src/vscode-dts/vscode.proposed.shareProvider.d.ts
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// https://github.com/microsoft/vscode/issues/176316
|
||||
|
||||
declare module 'vscode' {
|
||||
export interface TreeItem {
|
||||
shareableItem?: ShareableItem;
|
||||
}
|
||||
|
||||
export interface ShareableItem {
|
||||
resourceUri: Uri;
|
||||
selection?: Range;
|
||||
}
|
||||
|
||||
export interface ShareProvider {
|
||||
readonly id: string;
|
||||
readonly label: string;
|
||||
readonly priority: number;
|
||||
|
||||
provideShare(item: ShareableItem, token: CancellationToken): ProviderResult<Uri>;
|
||||
}
|
||||
|
||||
export namespace window {
|
||||
export function registerShareProvider(selector: DocumentSelector, provider: ShareProvider): Disposable;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue