mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01: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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user