Browse Source

Closes #467 - Adds copy remote url to clipboard command

main
Eric Amodio 6 years ago
parent
commit
58700ab15b
10 changed files with 169 additions and 56 deletions
  1. +3
    -0
      CHANGELOG.md
  2. +5
    -3
      README.md
  3. +40
    -6
      package.json
  4. +2
    -0
      src/commands.ts
  5. +1
    -0
      src/commands/common.ts
  6. +40
    -0
      src/commands/copyRemoteFileUrlToClipboard.ts
  7. +8
    -2
      src/commands/openFileInRemote.ts
  8. +19
    -15
      src/commands/openInRemote.ts
  9. +37
    -27
      src/git/remotes/provider.ts
  10. +14
    -3
      src/quickpicks/remotesQuickPick.ts

+ 3
- 0
CHANGELOG.md View File

@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] - 2018-08-16
### Added
- Adds *Copy Remote File Url to Clipboard* (`gitlens.copyRemoteFileUrlToClipboard`) command — copies the remote url of the current file and line to the clipboard — closes [#467](https://github.com/eamodio/vscode-gitlens/issues/467)
### Fixed
- Fixes [#471](https://github.com/eamodio/vscode-gitlens/issues/471) - Don't use Ctrl+Alt+[character] as a shortcut
- Fixes [#478](https://github.com/eamodio/vscode-gitlens/issues/478) - `suppressShowKeyBindingsNotice` gets saved even when it is not required

+ 5
- 3
README.md View File

@ -175,7 +175,7 @@ The repository view provides a full Git repository explorer, which has the follo
- Context menus for each revision (commit) provide
- *Open Commit in Remote* (if available), *Open All Changes*, *Open All Changes with Working Tree*, *Open Files*, *Open Revisions*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Show Commit Details*, *Compare with HEAD*, *Compare with Working Tree*, *Compare with Selected* (when available), *Select for Compare*, *Cherry Pick Commit (via Terminal)* (when available), *Push to Commit (via Terminal)* (when available), *Revert Commit (via Terminal)* (when available), *Checkout Commit (via Terminal)*, *Rebase to Commit (via Terminal)*, *Reset to Commit (via Terminal)*, *Create Branch (via Terminal)...*, *Create Tag (via Terminal)...*, and *Refresh* commands
- Context menus for each changed file provide
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Copy Remote File Url to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
- Inline toolbars for each changed file provide an *Open File* command
- **Remotes** — lists the remotes
@ -250,7 +250,7 @@ An on-demand, [customizable](#gitlens-results-explorer-settings "Jump to the Git
- Context menus for each revision (commit) provide
- *Open Commit in Remote* (if available), *Open All Changes*, *Open All Changes with Working Tree*, *Open Files*, *Open Revisions*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Show Commit Details*, *Compare with HEAD*, *Compare with Working Tree*, *Compare with Selected* (when available), *Select for Compare*, *Cherry Pick Commit (via Terminal)* (when available), *Push to Commit (via Terminal)* (when available), *Revert Commit (via Terminal)* (when available), *Checkout Commit (via Terminal)*, *Rebase to Commit (via Terminal)*, *Reset to Commit (via Terminal)*, *Create Branch (via Terminal)...*, *Create Tag (via Terminal)...*, and *Refresh* commands
- Context menus for each changed file provide
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Copy Remote File Url to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
#### Compare
- Provides a semi-persistent results view for comparison operations
@ -270,7 +270,7 @@ An on-demand, [customizable](#gitlens-results-explorer-settings "Jump to the Git
- **Changed Files** — lists the files changed between the compared revisions (branches or commits)
- Expands to a file-based view of all changed files
- Context menus for each changed file provide
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
- *Open Changes*, *Open Changes with Working File*, *Open File*, *Open Revision*, *Open File in Remote*, *Open Revision in Remote*, *Copy Commit ID to Clipboard*, *Copy Commit Message to Clipboard*, *Copy Remote File Url to Clipboard*, *Apply Changes*, *Compare with Selected* (when available), *Select for Compare*, and *Show Commit File Details* commands
---
### Code Lens
@ -568,6 +568,8 @@ An on-demand, [customizable](#gitlens-results-explorer-settings "Jump to the Git
- Adds a *Copy Commit Message to Clipboard* command (`gitlens.copyMessageToClipboard`) to copy the commit message of the current line to the clipboard or from the most recent commit to the current branch, if there is no current editor
- Adds a *Copy Remote File Url to Clipboard* command (`gitlens.copyRemoteFileUrlToClipboard`) to copy the remote url of the current file and line to the clipboard
- Adds a *Open Working File"* command (`gitlens.openWorkingFile`) to open the working file for the current file revision
- Adds a *Open Revision...* command (`gitlens.openFileRevision`) to open the selected revision for the current file

+ 40
- 6
package.json View File

@ -1710,13 +1710,18 @@
"category": "GitLens"
},
{
"command": "gitlens.copyShaToClipboard",
"title": "Copy Commit ID to Clipboard",
"command": "gitlens.copyMessageToClipboard",
"title": "Copy Commit Message to Clipboard",
"category": "GitLens"
},
{
"command": "gitlens.copyMessageToClipboard",
"title": "Copy Commit Message to Clipboard",
"command": "gitlens.copyRemoteFileUrlToClipboard",
"title": "Copy Remote File Url to Clipboard",
"category": "GitLens"
},
{
"command": "gitlens.copyShaToClipboard",
"title": "Copy Commit ID to Clipboard",
"category": "GitLens"
},
{
@ -2325,11 +2330,15 @@
"when": "gitlens:enabled"
},
{
"command": "gitlens.copyShaToClipboard",
"command": "gitlens.copyMessageToClipboard",
"when": "gitlens:activeFileStatus =~ /blameable/"
},
{
"command": "gitlens.copyMessageToClipboard",
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "gitlens:activeFileStatus =~ /tracked/ && gitlens:activeFileStatus =~ /remotes/"
},
{
"command": "gitlens.copyShaToClipboard",
"when": "gitlens:activeFileStatus =~ /blameable/"
},
{
@ -2682,6 +2691,11 @@
"command": "gitlens.copyMessageToClipboard",
"when": "editorTextFocus && gitlens:activeFileStatus =~ /blameable/ && config.gitlens.menus.editor.clipboard",
"group": "9_gitlens@2"
},
{
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "editorTextFocus && gitlens:activeFileStatus =~ /remotes/ && config.gitlens.menus.editor.clipboard",
"group": "9_gitlens@3"
}
],
"editor/title": [
@ -2781,6 +2795,11 @@
"command": "gitlens.diffWithPrevious",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && config.gitlens.menus.explorer.compare",
"group": "3_compare@1"
},
{
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "!explorerResourceIsRoot && !explorerResourceIsFolder && gitlens:enabled && gitlens:hasRemotes && config.gitlens.menus.explorer.remote",
"group": "9_gitlens@1"
}
],
"scm/resourceGroup/context": [
@ -2840,6 +2859,11 @@
"command": "gitlens.stashSave",
"when": "gitlens:enabled",
"group": "2_gitlens@1"
},
{
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "gitlens:enabled && gitlens:hasRemotes",
"group": "9_gitlens@1"
}
],
"view/title": [
@ -3156,6 +3180,11 @@
"group": "4_gitlens@1"
},
{
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "viewItem =~ /gitlens:file\\b/ && gitlens:hasRemotes",
"group": "5_gitlens@3"
},
{
"command": "gitlens.explorers.openFileRevisionInRemote",
"when": "viewItem == gitlens:file:commit && gitlens:hasRemotes",
"group": "4_gitlens@2"
@ -3166,6 +3195,11 @@
"group": "3_gitlens@2"
},
{
"command": "gitlens.copyRemoteFileUrlToClipboard",
"when": "viewItem =~ /gitlens:(history-file|status:file)\\b/ && gitlens:hasRemotes",
"group": "5_gitlens@3"
},
{
"command": "gitlens.showQuickFileHistory",
"when": "viewItem =~ /gitlens:file\\b/ && gitlens:gitExplorer:view == repository",
"group": "8_gitlens@1"

+ 2
- 0
src/commands.ts View File

@ -7,6 +7,7 @@ export * from './commands/common';
export * from './commands/clearFileAnnotations';
export * from './commands/closeUnchangedFiles';
export * from './commands/copyMessageToClipboard';
export * from './commands/copyRemoteFileUrlToClipboard';
export * from './commands/copyShaToClipboard';
export * from './commands/diffBranchWithBranch';
export * from './commands/diffDirectory';
@ -59,6 +60,7 @@ export function configureCommands(): void {
Container.context.subscriptions.push(new Commands.ClearFileAnnotationsCommand());
Container.context.subscriptions.push(new Commands.CloseUnchangedFilesCommand());
Container.context.subscriptions.push(new Commands.CopyMessageToClipboardCommand());
Container.context.subscriptions.push(new Commands.CopyRemoteFileUrlToClipboardCommand());
Container.context.subscriptions.push(new Commands.CopyShaToClipboardCommand());
Container.context.subscriptions.push(new Commands.DiffBranchWithBranchCommand());
Container.context.subscriptions.push(new Commands.DiffDirectoryCommand());

+ 1
- 0
src/commands/common.ts View File

@ -26,6 +26,7 @@ export enum Commands {
CloseUnchangedFiles = 'gitlens.closeUnchangedFiles',
ComputingFileAnnotations = 'gitlens.computingFileAnnotations',
CopyMessageToClipboard = 'gitlens.copyMessageToClipboard',
CopyRemoteFileUrlToClipboard = 'gitlens.copyRemoteFileUrlToClipboard',
CopyShaToClipboard = 'gitlens.copyShaToClipboard',
DiffDirectory = 'gitlens.diffDirectory',
DiffHeadWithBranch = 'gitlens.diffHeadWithBranch',

+ 40
- 0
src/commands/copyRemoteFileUrlToClipboard.ts View File

@ -0,0 +1,40 @@
'use strict';
import { commands, TextEditor, Uri } from 'vscode';
import {
ActiveEditorCommand,
CommandContext,
Commands,
isCommandViewContextWithBranch,
isCommandViewContextWithCommit
} from './common';
export interface CopyRemoteFileUrlToClipboardCommandArgs {
branch?: string;
range?: boolean;
}
export class CopyRemoteFileUrlToClipboardCommand extends ActiveEditorCommand {
constructor() {
super(Commands.CopyRemoteFileUrlToClipboard);
}
protected async preExecute(
context: CommandContext,
args: CopyRemoteFileUrlToClipboardCommandArgs = { range: true }
): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.range = false;
if (isCommandViewContextWithBranch(context)) {
args.branch = context.node.branch !== undefined ? context.node.branch.name : undefined;
}
return this.execute(context.editor, context.node.commit.uri, args);
}
return this.execute(context.editor, context.uri, args);
}
async execute(editor?: TextEditor, uri?: Uri, args: CopyRemoteFileUrlToClipboardCommandArgs = { range: true }) {
return commands.executeCommand(Commands.OpenFileInRemote, uri, { ...args, clipboard: true });
}
}

+ 8
- 2
src/commands/openFileInRemote.ts View File

@ -18,6 +18,7 @@ import { OpenInRemoteCommandArgs } from './openInRemote';
export interface OpenFileInRemoteCommandArgs {
branch?: string;
range?: boolean;
clipboard?: boolean;
}
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
@ -57,7 +58,11 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
if (branches.length > 1) {
const pick = await BranchesQuickPick.show(
branches,
`Open ${gitUri.getRelativePath()} in remote for which branch${GlyphChars.Ellipsis}`
args.clipboard
? `Copy url for ${gitUri.getRelativePath()} to clipboard for which branch${
GlyphChars.Ellipsis
}`
: `Open ${gitUri.getRelativePath()} in remote for which branch${GlyphChars.Ellipsis}`
);
if (pick === undefined) return undefined;
@ -92,7 +97,8 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
range: range,
sha: gitUri.sha
},
remotes
remotes,
clipboard: args.clipboard
} as OpenInRemoteCommandArgs);
}
catch (ex) {

+ 19
- 15
src/commands/openInRemote.ts View File

@ -11,6 +11,7 @@ export interface OpenInRemoteCommandArgs {
remote?: string;
remotes?: GitRemote[];
resource?: RemoteResource;
clipboard?: boolean;
goBackCommand?: CommandQuickPickItem;
}
@ -35,39 +36,41 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
try {
if (args.remotes.length === 1) {
this.ensureRemoteBranchName(args);
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource, args.clipboard);
return await command.execute();
}
const verb = args.clipboard ? 'Copy url for' : 'Open';
const suffix = args.clipboard ? `to clipboard from${GlyphChars.Ellipsis}` : `in${GlyphChars.Ellipsis}`;
let placeHolder = '';
switch (args.resource.type) {
case RemoteResourceType.Branch:
this.ensureRemoteBranchName(args);
placeHolder = `open ${args.resource.branch} branch in${GlyphChars.Ellipsis}`;
placeHolder = `${verb} ${args.resource.branch} branch ${suffix}`;
break;
case RemoteResourceType.Commit:
const shortSha = GitService.shortenSha(args.resource.sha);
placeHolder = `open commit ${shortSha} in${GlyphChars.Ellipsis}`;
placeHolder = `${verb} commit ${shortSha} ${suffix}`;
break;
case RemoteResourceType.File:
placeHolder = `open ${args.resource.fileName} in${GlyphChars.Ellipsis}`;
placeHolder = `${verb} ${args.resource.fileName} ${suffix}`;
break;
case RemoteResourceType.Revision:
if (args.resource.commit !== undefined && args.resource.commit instanceof GitLogCommit) {
if (args.resource.commit.status === 'D') {
args.resource.sha = args.resource.commit.previousSha;
placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
placeHolder = `${verb} ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
args.resource.commit.previousShortSha
} in${GlyphChars.Ellipsis}`;
} ${suffix}`;
}
else {
args.resource.sha = args.resource.commit.sha;
placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
placeHolder = `${verb} ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
args.resource.commit.shortSha
} in${GlyphChars.Ellipsis}`;
} ${suffix}`;
}
}
else {
@ -75,17 +78,18 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
args.resource.sha === undefined ? '' : GitService.shortenSha(args.resource.sha);
const shaSuffix = shortFileSha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${shortFileSha}` : '';
placeHolder = `open ${args.resource.fileName}${shaSuffix} in${GlyphChars.Ellipsis}`;
placeHolder = `${verb} ${args.resource.fileName}${shaSuffix} ${suffix}`;
}
break;
}
if (args.remotes.length === 1) {
const command = new OpenRemoteCommandQuickPickItem(args.remotes[0], args.resource);
return await command.execute();
}
const pick = await RemotesQuickPick.show(args.remotes, placeHolder, args.resource, args.goBackCommand);
const pick = await RemotesQuickPick.show(
args.remotes,
placeHolder,
args.resource,
args.clipboard,
args.goBackCommand
);
if (pick === undefined) return undefined;
return await pick.execute();

+ 37
- 27
src/git/remotes/provider.ts View File

@ -1,7 +1,8 @@
'use strict';
import { commands, Range, Uri } from 'vscode';
import { commands, Range, Uri, window } from 'vscode';
import { BuiltInCommands } from '../../constants';
import { GitLogCommit } from '../../gitService';
import { Logger } from '../../logger';
export enum RemoteResourceType {
Branch = 'branch',
@ -106,46 +107,55 @@ export abstract class RemoteProvider {
protected abstract getUrlForCommit(sha: string): string;
protected abstract getUrlForFile(fileName: string, branch?: string, sha?: string, range?: Range): string;
private async openUrl(url: string): Promise<{} | undefined> {
private async openUrl(url?: string): Promise<{} | undefined> {
if (url === undefined) return undefined;
return commands.executeCommand(BuiltInCommands.Open, Uri.parse(url));
}
async copy(resource: RemoteResource): Promise<{} | undefined> {
const url = this.url(resource);
if (url === undefined) return undefined;
try {
const clipboard = await import('clipboardy');
void (await clipboard.write(url));
return undefined;
}
catch (ex) {
if (ex.message.includes("Couldn't find the required `xsel` binary")) {
window.showErrorMessage(
`Unable to copy remote url, xsel is not installed. You can install it via \`sudo apt install xsel\``
);
return;
}
Logger.error(ex, 'CopyRemoteUrlToClipboardCommand');
return window.showErrorMessage(`Unable to copy remote url. See output channel for more details`);
}
}
open(resource: RemoteResource): Promise<{} | undefined> {
return this.openUrl(this.url(resource));
}
url(resource: RemoteResource): string | undefined {
switch (resource.type) {
case RemoteResourceType.Branch:
return this.openBranch(resource.branch);
return this.getUrlForBranch(resource.branch);
case RemoteResourceType.Branches:
return this.openBranches();
return this.getUrlForBranches();
case RemoteResourceType.Commit:
return this.openCommit(resource.sha);
return this.getUrlForCommit(resource.sha);
case RemoteResourceType.File:
return this.openFile(resource.fileName, resource.branch, undefined, resource.range);
return this.getUrlForFile(resource.fileName, resource.branch, undefined, resource.range);
case RemoteResourceType.Repo:
return this.openRepo();
return this.getUrlForRepository();
case RemoteResourceType.Revision:
return this.openFile(resource.fileName, resource.branch, resource.sha, resource.range);
return this.getUrlForFile(resource.fileName, resource.branch, resource.sha, resource.range);
}
}
openRepo() {
return this.openUrl(this.getUrlForRepository());
}
openBranches() {
return this.openUrl(this.getUrlForBranches());
}
openBranch(branch: string) {
return this.openUrl(this.getUrlForBranch(branch));
}
openCommit(sha: string) {
return this.openUrl(this.getUrlForCommit(sha));
}
openFile(fileName: string, branch?: string, sha?: string, range?: Range) {
return this.openUrl(this.getUrlForFile(fileName, branch, sha, range));
return undefined;
}
}

+ 14
- 3
src/quickpicks/remotesQuickPick.ts View File

@ -18,10 +18,18 @@ export class OpenRemoteCommandQuickPickItem extends CommandQuickPickItem {
private remote: GitRemote;
private resource: RemoteResource;
constructor(remote: GitRemote, resource: RemoteResource) {
constructor(
remote: GitRemote,
resource: RemoteResource,
public readonly clipboard?: boolean
) {
super(
{
label: `$(link-external) Open ${getNameFromRemoteResource(resource)} in ${remote.provider!.name}`,
label: clipboard
? `$(link-external) Copy ${getNameFromRemoteResource(resource)} Url to Clipboard from ${
remote.provider!.name
}`
: `$(link-external) Open ${getNameFromRemoteResource(resource)} in ${remote.provider!.name}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} $(repo) ${remote.provider!.path}`
},
undefined,
@ -33,6 +41,8 @@ export class OpenRemoteCommandQuickPickItem extends CommandQuickPickItem {
}
async execute(): Promise<{} | undefined> {
if (this.clipboard) return this.remote.provider!.copy(this.resource);
return this.remote.provider!.open(this.resource);
}
}
@ -142,9 +152,10 @@ export class RemotesQuickPick {
remotes: GitRemote[],
placeHolder: string,
resource: RemoteResource,
clipboard?: boolean,
goBackCommand?: CommandQuickPickItem
): Promise<OpenRemoteCommandQuickPickItem | CommandQuickPickItem | undefined> {
const items = remotes.map(r => new OpenRemoteCommandQuickPickItem(r, resource)) as (
const items = remotes.map(r => new OpenRemoteCommandQuickPickItem(r, resource, clipboard)) as (
| OpenRemoteCommandQuickPickItem
| CommandQuickPickItem)[];

Loading…
Cancel
Save