Browse Source

Adds experimental support for Open in GitHub

main
Eric Amodio 7 years ago
parent
commit
4f84c03275
21 changed files with 399 additions and 43 deletions
  1. +2
    -1
      CHANGELOG.md
  2. +5
    -0
      package.json
  3. +1
    -1
      src/commands/showQuickBranchHistory.ts
  4. +8
    -7
      src/commands/showQuickCommitDetails.ts
  5. +5
    -0
      src/configuration.ts
  6. +11
    -0
      src/git/git.ts
  7. +4
    -0
      src/git/gitUri.ts
  8. +32
    -0
      src/git/hosting/factory.ts
  9. +26
    -0
      src/git/hosting/github.ts
  10. +48
    -0
      src/git/hosting/hostingProvider.ts
  11. +1
    -0
      src/git/models/models.ts
  12. +27
    -0
      src/git/models/remote.ts
  13. +24
    -8
      src/gitService.ts
  14. +1
    -0
      src/quickPicks.ts
  15. +15
    -9
      src/quickPicks/branchHistory.ts
  16. +8
    -5
      src/quickPicks/commitDetails.ts
  17. +13
    -3
      src/quickPicks/commitFileDetails.ts
  18. +14
    -8
      src/quickPicks/fileHistory.ts
  19. +139
    -0
      src/quickPicks/remotes.ts
  20. +1
    -1
      src/system.ts
  21. +14
    -0
      src/system/array.ts

+ 2
- 1
CHANGELOG.md View File

@ -1,7 +1,8 @@
## Release Notes
### 2.13.0
- Adds an update notification (for feature releases) -- please file an issue if this it too much
- Adds experimental support for `Open in GitHub` to the relevant quick picks -- need to enable it via `"gitlens.experimental.openInHostingProvider": true`
- Adds an update notification for feature releases
- Adds `Show Branch History` command (`gitlens.showQuickBranchHistory`) to show the history of the selected branch
- Adds `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`) to re-open the previously opened quick pick - helps to get back to previous context
- Adds `alt+-` shortcut for the `Show Last Opened Quick Pick` command (`gitlens.showLastQuickPick`)

+ 5
- 0
package.json View File

@ -350,6 +350,11 @@
"type": "boolean",
"default": false,
"description": "Specifies whether or not to toggle whitespace off then showing blame annotations (*may* be required by certain fonts/themes)"
},
"gitlens.experimental.openInHostingProvider": {
"type": "boolean",
"default": false,
"description": "(experimental) Specifies whether or not to show Open X in Hosting Provider commands in quick picks"
}
}
},

+ 1
- 1
src/commands/showQuickBranchHistory.ts View File

@ -50,7 +50,7 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await BranchHistoryQuickPick.show(log, gitUri, branch, progressCancellation, goBackCommand, nextPageCommand);
const pick = await BranchHistoryQuickPick.show(this.git, log, gitUri, branch, progressCancellation, goBackCommand, nextPageCommand);
if (!pick) return undefined;
if (pick instanceof CommandQuickPickItem) {

+ 8
- 7
src/commands/showQuickCommitDetails.ts View File

@ -75,19 +75,20 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
}, Commands.ShowQuickCurrentBranchHistory, [new GitUri(commit.uri, commit)]);
}
const pick = await CommitDetailsQuickPick.show(this.git, commit as GitLogCommit, uri, goBackCommand, repoLog);
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, commit, goBackCommand, repoLog]);
const pick = await CommitDetailsQuickPick.show(this.git, commit as GitLogCommit, uri, goBackCommand, currentCommand, repoLog);
if (!pick) return undefined;
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) {
return pick.execute();
}
return commands.executeCommand(Commands.ShowQuickCommitFileDetails, pick.gitUri, pick.sha, undefined,
// Create a command to get back to where we are right now
new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to details of \u00a0$(git-commit) ${pick.shortSha}`
}, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), sha, commit, goBackCommand, repoLog]));
return commands.executeCommand(Commands.ShowQuickCommitFileDetails, pick.gitUri, pick.sha, undefined, currentCommand);
}
catch (ex) {
Logger.error('[GitLens.ShowQuickCommitDetailsCommand]', ex);

+ 5
- 0
src/configuration.ts View File

@ -123,9 +123,14 @@ export interface IAdvancedConfig {
};
}
export interface IExperimentalConfig {
openInHostingProvider: boolean;
}
export interface IConfig {
blame: IBlameConfig;
codeLens: ICodeLensesConfig;
experimental: IExperimentalConfig;
statusBar: IStatusBarConfig;
advanced: IAdvancedConfig;
}

+ 11
- 0
src/git/git.ts View File

@ -10,6 +10,7 @@ export * from './models/models';
export * from './parsers/blameParser';
export * from './parsers/logParser';
export * from './parsers/statusParser';
export * from './hosting/hostingProvider';
let git: IGit;
@ -210,6 +211,16 @@ export class Git {
return gitCommand(root, ...params);
}
static remote(repoPath: string): Promise<string> {
const params = ['remote', '-v'];
return gitCommand(repoPath, ...params);
}
static remote_url(repoPath: string, remote: string): Promise<string> {
const params = ['remote', 'get-url', remote];
return gitCommand(repoPath, ...params);
}
static status(repoPath: string): Promise<string> {
const params = ['status', '--porcelain=v2', '--branch'];
return gitCommand(repoPath, ...params);

+ 4
- 0
src/git/gitUri.ts View File

@ -71,6 +71,10 @@ export class GitUri extends Uri {
: `${path.basename(this.fsPath)}${separator}${directory}`;
}
getRelativePath(): string {
return Git.normalizePath(path.relative(this.repoPath, this.fsPath));
}
static async fromUri(uri: Uri, git: GitService) {
if (uri instanceof GitUri) return uri;

+ 32
- 0
src/git/hosting/factory.ts View File

@ -0,0 +1,32 @@
'use strict';
import { HostingProvider } from './hostingProvider';
import { GitHubService } from './github';
import { Logger } from '../../logger';
export { HostingProvider };
const serviceMap = new Map<string, (domain: string, path: string) => HostingProvider>([
['github.com', (domain: string, path: string) => new GitHubService(domain, path)]
]);
const UrlRegex = /^(?:git:\/\/(.*?)\/|https:\/\/(.*?)\/|http:\/\/(.*?)\/|git@(.*):\/\/|ssh:\/\/git@(.*?)\/)(.*)$/;
export class HostingProviderFactory {
static getHostingProvider(url: string): HostingProvider {
try {
const match = UrlRegex.exec(url);
const domain = match[1] || match[2] || match[3] || match[4] || match[5];
const path = match[6].replace(/\.git/, '');
const serviceCreator = serviceMap.get(domain);
if (!serviceCreator) return undefined;
return serviceCreator(domain, path);
}
catch (ex) {
Logger.error(ex);
return undefined;
}
}
}

+ 26
- 0
src/git/hosting/github.ts View File

@ -0,0 +1,26 @@
'use strict';
import { HostingProvider } from './hostingProvider';
export class GitHubService extends HostingProvider {
constructor(public domain: string, public path: string) {
super(domain, path);
}
get name() {
return 'GitHub';
}
protected getUrlForBranch(branch: string): string {
return `${this.baseUrl}/tree/${branch}`;
}
protected getUrlForCommit(sha: string): string {
return `${this.baseUrl}/commit/${sha}`;
}
protected getUrlForFile(fileName: string, sha?: string): string {
if (sha) return `${this.baseUrl}/blob/${sha}/${fileName}`;
return `${this.baseUrl}?path=${fileName}`;
}
}

+ 48
- 0
src/git/hosting/hostingProvider.ts View File

@ -0,0 +1,48 @@
'use strict';
import { commands, Uri } from 'vscode';
import { BuiltInCommands } from '../../constants';
export type HostingProviderOpenType = 'branch' | 'commit' | 'file';
export abstract class HostingProvider {
constructor(public domain: string, public path: string) { }
abstract get name(): string;
protected get baseUrl() {
return `https://${this.domain}/${this.path}`;
}
protected abstract getUrlForBranch(branch: string): string;
protected abstract getUrlForCommit(sha: string): string;
protected abstract getUrlForFile(fileName: string, sha?: string): string;
private async _openUrl(url: string): Promise<{}> {
return url && commands.executeCommand(BuiltInCommands.Open, Uri.parse(url));
}
open(type: 'branch', branch: string): Promise<{}>;
open(type: 'commit', sha: string): Promise<{}>;
open(type: 'file', fileName: string, sha?: string): Promise<{}>;
open(type: HostingProviderOpenType, ...args: string[]): Promise<{}>;
open(type: HostingProviderOpenType, branchOrShaOrFileName: string, sha?: string): Promise<{}> {
switch (type) {
case 'branch': return this.openBranch(branchOrShaOrFileName);
case 'commit': return this.openCommit(branchOrShaOrFileName);
case 'file': return this.openFile(branchOrShaOrFileName, sha);
}
}
openBranch(branch: string) {
return this._openUrl(this.getUrlForBranch(branch));
}
openCommit(sha: string) {
return this._openUrl(this.getUrlForCommit(sha));
}
openFile(fileName: string, sha?: string) {
return this._openUrl(this.getUrlForFile(fileName, sha));
}
}

+ 1
- 0
src/git/models/models.ts View File

@ -4,4 +4,5 @@ export * from './branch';
export * from './commit';
export * from './log';
export * from './logCommit';
export * from './remote';
export * from './status';

+ 27
- 0
src/git/models/remote.ts View File

@ -0,0 +1,27 @@
'use strict';
import { HostingProvider, HostingProviderFactory } from '../hosting/factory';
export type GitRemoteType = 'fetch' | 'push';
export class GitRemote {
name: string;
url: string;
type: GitRemoteType;
provider?: HostingProvider;
constructor(remote: string) {
remote = remote.trim();
const [name, info] = remote.split('\t');
this.name = name;
const [url, typeInfo] = info.split(' ');
this.url = url;
this.type = typeInfo.substring(1, typeInfo.length - 1) as GitRemoteType;
this.provider = HostingProviderFactory.getHostingProvider(this.url);
}
}

+ 24
- 8
src/gitService.ts View File

@ -4,7 +4,7 @@ import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, l
import { CommandContext, setCommandContext } from './commands';
import { CodeLensVisibility, IConfig } from './configuration';
import { DocumentSchemes, WorkspaceState } from './constants';
import { Git, GitBlameParser, GitBranch, GitCommit, GitStatusFile, GitLogParser, GitStatusParser, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitLog, IGitStatus } from './git/git';
import { Git, GitBlameParser, GitBranch, GitCommit, GitLogParser, GitRemote, GitStatusFile, GitStatusParser, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitLog, IGitStatus } from './git/git';
import { IGitUriData, GitUri } from './git/gitUri';
import { GitCodeLensProvider } from './gitCodeLensProvider';
import { Logger } from './logger';
@ -63,6 +63,7 @@ export class GitService extends Disposable {
public repoPath: string;
private _gitCache: Map<string, GitCacheEntry> | undefined;
private _remotesCache: GitRemote[];
private _cacheDisposable: Disposable | undefined;
private _uriCache: Map<string, UriCacheEntry> | undefined;
@ -585,6 +586,21 @@ export class GitService extends Disposable {
return locations;
}
async getRemotes(repoPath: string): Promise<GitRemote[]> {
if (!this.config.experimental || !this.config.experimental.openInHostingProvider) return Promise.resolve([]);
Logger.log(`getRemotes('${repoPath}')`);
if (this.UseGitCaching && this._remotesCache) return this._remotesCache;
const data = await Git.remote(repoPath);
const remotes = data.split('\n').filter(_ => !!_).map(_ => new GitRemote(_));
if (this.UseGitCaching) {
this._remotesCache = remotes;
}
return remotes;
}
getRepoPath(cwd: string): Promise<string> {
return Git.getRepoPath(cwd);
}
@ -618,13 +634,6 @@ export class GitService extends Disposable {
return GitStatusParser.parse(data, repoPath);
}
async isFileUncommitted(uri: GitUri): Promise<boolean> {
Logger.log(`isFileUncommitted('${uri.repoPath}', '${uri.fsPath}')`);
const status = await this.getStatusForFile(uri.repoPath, uri.fsPath);
return !!status;
}
async getVersionedFile(repoPath: string, fileName: string, sha: string) {
Logger.log(`getVersionedFile('${repoPath}', '${fileName}', ${sha})`);
@ -664,6 +673,13 @@ export class GitService extends Disposable {
this.hasGitUriForFile(editor));
}
async isFileUncommitted(uri: GitUri): Promise<boolean> {
Logger.log(`isFileUncommitted('${uri.repoPath}', '${uri.fsPath}')`);
const status = await this.getStatusForFile(uri.repoPath, uri.fsPath);
return !!status;
}
openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) {
Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`);

+ 1
- 0
src/quickPicks.ts View File

@ -6,4 +6,5 @@ export * from './quickPicks/commitDetails';
export * from './quickPicks/commitFileDetails';
export * from './quickPicks/branchHistory';
export * from './quickPicks/fileHistory';
export * from './quickPicks/remotes';
export * from './quickPicks/repoStatus';

+ 15
- 9
src/quickPicks/branchHistory.ts View File

@ -1,10 +1,9 @@
'use strict';
import { Iterables } from '../system';
import { Arrays, Iterables } from '../system';
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, Keyboard, KeyNoopCommand } from '../commands';
import { GitUri, IGitLog } from '../gitService';
import { CommitQuickPickItem } from './gitQuickPicks';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks';
import { GitService, GitUri, IGitLog } from '../gitService';
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, OpenRemotesCommandQuickPickItem, showQuickPickProgress } from '../quickPicks';
export class BranchHistoryQuickPick {
@ -17,9 +16,19 @@ export class BranchHistoryQuickPick {
});
}
static async show(log: IGitLog, uri: GitUri, branch: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem): Promise<CommitQuickPickItem | CommandQuickPickItem | undefined> {
static async show(git: GitService, log: IGitLog, uri: GitUri, branch: string, progressCancellation: CancellationTokenSource, goBackCommand?: CommandQuickPickItem, nextPageCommand?: CommandQuickPickItem): Promise<CommitQuickPickItem | CommandQuickPickItem | undefined> {
const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileNames}`))) as (CommitQuickPickItem | CommandQuickPickItem)[];
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to \u00a0$(git-branch) ${branch} history`
}, Commands.ShowQuickBranchHistory, [uri, branch, log.maxCount, goBackCommand, log]);
const remotes = Arrays.uniqueBy(await git.getRemotes(git.repoPath), _ => _.url, _ => !!_.provider);
if (remotes.length) {
items.splice(0, 0, new OpenRemotesCommandQuickPickItem(remotes, 'branch', branch, currentCommand));
}
let previousPageCommand: CommandQuickPickItem;
if ((log.truncated || (uri && uri.sha))) {
@ -42,10 +51,7 @@ export class BranchHistoryQuickPick {
new GitUri(Uri.file(log.repoPath), { fileName: '', repoPath: log.repoPath }),
branch,
undefined,
new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to \u00a0$(git-branch) ${branch} history`
}, Commands.ShowQuickBranchHistory, [uri, branch, log.maxCount, goBackCommand, log])
currentCommand
]));
}

+ 8
- 5
src/quickPicks/commitDetails.ts View File

@ -1,10 +1,9 @@
'use strict';
import { Iterables } from '../system';
import { Arrays, Iterables } from '../system';
import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, Keyboard, KeyNoopCommand } from '../commands';
import { GitLogCommit, GitService, IGitLog } from '../gitService';
import { CommitWithFileStatusQuickPickItem } from './gitQuickPicks';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks';
import { CommandQuickPickItem, CommitWithFileStatusQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFilesCommandQuickPickItem, OpenRemotesCommandQuickPickItem } from '../quickPicks';
import * as moment from 'moment';
import * as path from 'path';
@ -36,7 +35,7 @@ export class OpenCommitWorkingTreeFilesCommandQuickPickItem extends OpenFilesCom
export class CommitDetailsQuickPick {
static async show(git: GitService, commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, repoLog?: IGitLog): Promise<CommitWithFileStatusQuickPickItem | CommandQuickPickItem | undefined> {
static async show(git: GitService, commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, repoLog?: IGitLog): Promise<CommitWithFileStatusQuickPickItem | CommandQuickPickItem | undefined> {
const items: (CommitWithFileStatusQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new CommitWithFileStatusQuickPickItem(commit, fs.fileName, fs.status));
let index = 0;
@ -51,6 +50,11 @@ export class CommitDetailsQuickPick {
description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.message}`
}, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message]));
const remotes = Arrays.uniqueBy(await git.getRemotes(git.repoPath), _ => _.url, _ => !!_.provider);
if (remotes.length) {
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, 'commit', commit.sha, currentCommand));
}
items.splice(index++, 0, new CommandQuickPickItem({
label: `$(git-compare) Directory Compare with Previous Commit`,
description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousShortSha || `${commit.shortSha}^`} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.shortSha}`
@ -61,7 +65,6 @@ export class CommitDetailsQuickPick {
description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.shortSha} \u00a0 $(git-compare) \u00a0 $(file-directory) Working Tree`
}, Commands.DiffDirectory, [uri, commit.sha]));
const added = commit.fileStatuses.filter(_ => _.status === 'A' || _.status === '?').length;
const deleted = commit.fileStatuses.filter(_ => _.status === 'D').length;
const changed = commit.fileStatuses.filter(_ => _.status !== 'A' && _.status !== '?' && _.status !== 'D').length;

+ 13
- 3
src/quickPicks/commitFileDetails.ts View File

@ -1,9 +1,9 @@
'use strict';
import { Iterables } from '../system';
import { Arrays, Iterables } from '../system';
import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, Keyboard, KeyNoopCommand } from '../commands';
import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './quickPicks';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem, OpenRemotesCommandQuickPickItem } from '../quickPicks';
import * as moment from 'moment';
import * as path from 'path';
@ -75,7 +75,17 @@ export class CommitFileDetailsQuickPick {
}, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message]));
items.push(new OpenCommitFileCommandQuickPickItem(commit));
items.push(new OpenCommitWorkingTreeFileCommandQuickPickItem(commit));
if (commit.workingFileName) {
items.push(new OpenCommitWorkingTreeFileCommandQuickPickItem(commit));
}
const remotes = Arrays.uniqueBy(await git.getRemotes(git.repoPath), _ => _.url, _ => !!_.provider);
if (remotes.length) {
items.push(new OpenRemotesCommandQuickPickItem(remotes, 'file', commit.fileName, commit.sha, undefined, currentCommand));
if (commit.workingFileName) {
items.push(new OpenRemotesCommandQuickPickItem(remotes, 'file', commit.workingFileName, undefined, 'Working File', currentCommand));
}
}
if (commit.workingFileName) {
items.push(new CommandQuickPickItem({

+ 14
- 8
src/quickPicks/fileHistory.ts View File

@ -1,10 +1,9 @@
'use strict';
import { Iterables } from '../system';
import { Arrays, Iterables } from '../system';
import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, Keyboard, KeyNoopCommand } from '../commands';
import { GitService, GitUri, IGitLog } from '../gitService';
import { CommitQuickPickItem } from './gitQuickPicks';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks';
import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut, OpenRemotesCommandQuickPickItem, showQuickPickProgress } from '../quickPicks';
import * as path from 'path';
export class FileHistoryQuickPick {
@ -74,21 +73,28 @@ export class FileHistoryQuickPick {
}
}
const currentCommand = new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from \u00a0$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [uri, log.range, log.maxCount, undefined, log]);
// Only show the full repo option if we are the root
if (!goBackCommand) {
items.splice(index, 0, new CommandQuickPickItem({
items.splice(index++, 0, new CommandQuickPickItem({
label: `$(history) Show Branch History`,
description: `\u00a0 \u2014 \u00a0\u00a0 shows the current branch history`
}, Commands.ShowQuickCurrentBranchHistory,
[
undefined,
new CommandQuickPickItem({
label: `go back \u21A9`,
description: `\u00a0 \u2014 \u00a0\u00a0 to history of \u00a0$(file-text) ${path.basename(uri.fsPath)}${uri.sha ? ` from \u00a0$(git-commit) ${uri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [uri, log.range, log.maxCount, undefined, log])
currentCommand
]));
}
const remotes = Arrays.uniqueBy(await git.getRemotes(git.repoPath), _ => _.url, _ => !!_.provider);
if (remotes.length) {
items.splice(index++, 0, new OpenRemotesCommandQuickPickItem(remotes, 'file', uri.getRelativePath(), uri.sha, undefined, currentCommand));
}
if (goBackCommand) {
items.splice(0, 0, goBackCommand);
}

+ 139
- 0
src/quickPicks/remotes.ts View File

@ -0,0 +1,139 @@
'use strict';
import { QuickPickOptions, window } from 'vscode';
import { GitRemote, HostingProviderOpenType } from '../gitService';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks';
import * as path from 'path';
export class OpenRemoteCommandQuickPickItem extends CommandQuickPickItem {
private type: HostingProviderOpenType;
private remote: GitRemote;
constructor(remote: GitRemote, type: HostingProviderOpenType, ...args: string[]);
constructor(remote: GitRemote, type: HostingProviderOpenType, branchOrShaOrFileName: string, fileSha?: string, name?: string) {
if (!name) {
name = `${type[0].toUpperCase()}${type.substring(1)}`;
}
super({
label: `$(link-external) Open ${name} in ${remote.provider.name}`,
description: `\u00a0 \u2014 \u00a0\u00a0 $(repo) ${remote.provider.path}`
}, undefined, undefined);
this.type = type;
this.remote = remote;
this.args = [branchOrShaOrFileName, fileSha];
}
async execute(): Promise<{}> {
return this.remote.provider.open(this.type, ...this.args);
}
}
export class OpenRemotesCommandQuickPickItem extends CommandQuickPickItem {
private goBackCommand: CommandQuickPickItem;
private name: string;
private placeHolder: string;
private remotes: GitRemote[];
private type: HostingProviderOpenType;
constructor(remotes: GitRemote[], type: 'branch', branch: string, goBackCommand?: CommandQuickPickItem);
constructor(remotes: GitRemote[], type: 'commit', sha: string, goBackCommand?: CommandQuickPickItem);
constructor(remotes: GitRemote[], type: 'file', fileName: string, sha?: string, name?: string, goBackCommand?: CommandQuickPickItem);
constructor(remotes: GitRemote[], type: HostingProviderOpenType, branchOrShaOrFileName: string, shaOrGoBackCommand?: string | CommandQuickPickItem, name?: string, goBackCommand?: CommandQuickPickItem) {
let fileSha: string;
if (typeof shaOrGoBackCommand === 'string') {
fileSha = shaOrGoBackCommand;
}
else if (!goBackCommand) {
goBackCommand = shaOrGoBackCommand;
}
let description: string;
let placeHolder: string;
switch (type) {
case 'branch':
name = name || 'Branch';
description = `$(git-branch) ${branchOrShaOrFileName}`;
placeHolder = `open ${branchOrShaOrFileName} ${name.toLowerCase()} in\u2026`;
break;
case 'commit':
const shortSha = branchOrShaOrFileName.substring(0, 8);
name = name || 'Commit';
description = `$(git-commit) ${shortSha}`;
placeHolder = `open ${name.toLowerCase()} ${shortSha} in\u2026`;
break;
case 'file':
const fileName = path.basename(branchOrShaOrFileName);
const shortFileSha = (fileSha && fileSha.substring(0, 8)) || '';
const shaSuffix = shortFileSha ? ` \u00a0\u2022\u00a0 ${shortFileSha}` : '';
name = name || 'File';
description = `$(file-text) ${fileName}${shortFileSha ? ` in \u00a0$(git-commit) ${shortFileSha}` : ''}`;
placeHolder = `open ${branchOrShaOrFileName}${shaSuffix} in\u2026`;
break;
}
const remote = remotes[0];
if (remotes.length === 1) {
super({
label: `$(link-external) Open ${name} in ${remote.provider.name}`,
description: `\u00a0 \u2014 \u00a0\u00a0 $(repo) ${remote.provider.path} \u00a0\u2022\u00a0 ${description}`
}, undefined, undefined);
}
else {
const provider = remotes.every(_ => _.provider.name === remote.provider.name)
? remote.provider.name
: 'Hosting Provider';
super({
label: `$(link-external) Open ${name} in ${provider}\u2026`,
description: `\u00a0 \u2014 \u00a0\u00a0 ${description}`
}, undefined, undefined);
}
this.goBackCommand = goBackCommand;
this.name = name;
this.placeHolder = placeHolder;
this.remotes = remotes;
this.type = type;
this.args = [branchOrShaOrFileName, fileSha];
}
async execute(): Promise<{}> {
if (this.remotes.length === 1) {
const command = new OpenRemoteCommandQuickPickItem(this.remotes[0], this.type, ...this.args);
return command.execute();
}
const pick = await RemotesQuickPick.show(this.remotes, this.placeHolder, this.type, this.args, this.name, this.goBackCommand);
return pick && pick.execute();
}
}
export class RemotesQuickPick {
static async show(remotes: GitRemote[], placeHolder: string, type: HostingProviderOpenType, args: string[], name: string, goBackCommand?: CommandQuickPickItem): Promise<OpenRemoteCommandQuickPickItem | CommandQuickPickItem | undefined> {
const items = remotes.map(_ => new OpenRemoteCommandQuickPickItem(_, type, ...args, name)) as (OpenRemoteCommandQuickPickItem | CommandQuickPickItem)[];
if (goBackCommand) {
items.splice(0, 0, goBackCommand);
}
// const scope = await Keyboard.instance.beginScope({ left: goBackCommand });
const pick = await window.showQuickPick(items,
{
placeHolder: placeHolder,
ignoreFocusOut: getQuickPickIgnoreFocusOut()
} as QuickPickOptions);
if (!pick) return undefined;
// await scope.dispose();
return pick;
}
}

+ 1
- 1
src/system.ts View File

@ -1,5 +1,5 @@
'use strict';
// export * from './system/array';
export * from './system/array';
// export * from './system/disposable';
// export * from './system/element';
// export * from './system/event';

+ 14
- 0
src/system/array.ts View File

@ -0,0 +1,14 @@
'use strict';
export namespace Arrays {
export function uniqueBy<T>(array: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] {
const uniqueValues = Object.create(null);
return array.filter(_ => {
const value = accessor(_);
if (uniqueValues[value]) return false;
uniqueValues[value] = accessor;
return predicate ? predicate(_) : true;
});
}
}

Loading…
Cancel
Save