diff --git a/CHANGELOG.md b/CHANGELOG.md index 466e5c34..6d796322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [v1.1.0] + +### Added +- `copy-github-permalink.show`: Show Github's permalink in VS Code. + ## [v1.0.2] ### Fixed diff --git a/README.md b/README.md index cff61189..a18ed39a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ This extension by default will copy permalink to your `origin/master` because it To activate, enter these commands in the Command Palette: - `copy-github-permalink.copy`: Copy permalink of the selected line. +- `copy-github-permalink.show`: Show Github's permalink in VS Code. ## Extension Settings diff --git a/package.json b/package.json index a590f0ac..9be7cf02 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "displayName": "Copy Github Permalink", "description": "Copy Github Permalink", "icon": "img/ico.png", - "version": "1.0.2", + "version": "1.1.0", "homepage": "https://github.com/tejanium/vscode-copy-github-permalink/blob/master/README.md", "bugs": { "url": "https://github.com/tejanium/vscode-copy-github-permalink/issues" @@ -15,13 +15,14 @@ "url": "https://github.com/tejanium/vscode-copy-github-permalink" }, "engines": { - "vscode": "^1.47.0" + "vscode": "^1.49.0" }, "categories": [ "Other" ], "activationEvents": [ - "onCommand:copy-github-permalink.copy" + "onCommand:copy-github-permalink.copy", + "onCommand:copy-github-permalink.show" ], "main": "./dist/extension", "contributes": { @@ -29,6 +30,9 @@ { "command": "copy-github-permalink.copy", "title": "Github Permalink: Copy" + }, { + "command": "copy-github-permalink.show", + "title": "Github Permalink: Show" } ], "configuration": { @@ -46,7 +50,7 @@ "vscode:prepublish": "webpack --mode production", "compile": "webpack --mode development", "test-compile": "tsc -p ./", - "lint": "eslint src --ext ts", + "lint": "eslint src --ext ts --fix", "watch": "webpack --mode development --watch --info-verbosity verbose", "pretest": "yarn --frozen-lockfile && yarn compile && yarn test-compile && yarn lint", "test": "node ./out/test/runTest.js", diff --git a/src/extension.ts b/src/extension.ts index 92796eca..3cbfe582 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,8 +1,9 @@ import * as vscode from 'vscode'; +import { Document } from './model/document'; import { Permalink } from './model/permalink'; export function activate(context: vscode.ExtensionContext) { - let disposable = vscode.commands.registerCommand('copy-github-permalink.copy', async () => { + const copy = vscode.commands.registerCommand('copy-github-permalink.copy', async () => { const editor = vscode.window.activeTextEditor; if (editor && editor.document.uri.scheme === 'file') { @@ -20,7 +21,24 @@ export function activate(context: vscode.ExtensionContext) { } }); - context.subscriptions.push(disposable); + context.subscriptions.push(copy); + + const show = vscode.commands.registerCommand('copy-github-permalink.show', async () => { + const input = await vscode.window.showInputBox(); + + if (input) { + const document = new Document(input); + const showOpts = { preview: false }; + + if (document.range) { + Object.assign(showOpts, { selection: document.range }); + } + + vscode.window.showTextDocument(await document.document(), showOpts); + } + }); + + context.subscriptions.push(show); } function getBranch(): string { diff --git a/src/model/document.ts b/src/model/document.ts new file mode 100644 index 00000000..3bc244ad --- /dev/null +++ b/src/model/document.ts @@ -0,0 +1,38 @@ +import * as vscode from 'vscode'; + +export class Document { + private path: string | undefined; + private position: string | undefined; + + constructor(private permalink: string) { + [this.path, this.position] = this.relativePathWithLines.split('#') || []; + } + + async document(): Promise { + const root = vscode.workspace.workspaceFolders![0].uri.fsPath; + + return await vscode.workspace.openTextDocument(`${root}/${this.path}`); + } + + get range(): vscode.Range | undefined { + if (!this.position) { + return; + } + + const [start, end] = this.position.split("-") || []; + const startPosition = this.getPosition(start, -1); + const endPosition = this.getPosition(end); + + return new vscode.Range(startPosition, endPosition); + } + + private get relativePathWithLines(): string { + return this.permalink.split('blob').pop()?.split('/').slice(2).join('/') || ''; + } + + private getPosition(raw: string | undefined, offset: number = 0) { + const safeRaw = raw?.replace(/\D/g, '') || '0'; + + return new vscode.Position(parseInt(safeRaw) + offset, 0); + } +} diff --git a/src/test/suite/extension.test.ts b/src/test/suite/extension.test.ts index 5d3e49ab..6e7962b2 100644 --- a/src/test/suite/extension.test.ts +++ b/src/test/suite/extension.test.ts @@ -28,12 +28,18 @@ function configureBranch(sandbox: sinon.SinonSandbox, branch: string): void { } as vscode.WorkspaceConfiguration); }; +async function getFixtureFile(path: string): Promise { + const root = vscode.workspace.workspaceFolders![0].uri.fsPath; + + return await vscode.workspace.openTextDocument(`${root}/${path}`); +} + suite('Test commands', () => { let sandbox: sinon.SinonSandbox; before(async () => { - const root = vscode.workspace.workspaceFolders![0].uri.fsPath; - const document = await vscode.workspace.openTextDocument(`${root}/test/fixtures/file.txt`); + const document = await getFixtureFile('test/fixtures/file.txt'); + await vscode.window.showTextDocument(document); }); @@ -47,7 +53,7 @@ suite('Test commands', () => { suite('copy-github-permalink.copy', () => { test('Display copied information and put the link to clipboard', async () => { - const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as sinon.SinonStub<[string, any?], Thenable>; + const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'HEAD'); mockGit(); @@ -59,7 +65,7 @@ suite('Test commands', () => { }); test('Git remote is HTTP', async () => { - const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as sinon.SinonStub<[string, any?], Thenable>; + const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'HEAD'); mockGit('http://github.com/owner/name.git'); @@ -71,7 +77,7 @@ suite('Test commands', () => { }); test('Git remote is HTTPS', async () => { - const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as sinon.SinonStub<[string, any?], Thenable>; + const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'HEAD'); mockGit('https://github.com/owner/name.git'); @@ -83,7 +89,7 @@ suite('Test commands', () => { }); test('Display copied information and put the link of all lines to clipboard', async () => { - const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as sinon.SinonStub<[string, any?], Thenable>; + const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'HEAD'); mockGit(); @@ -96,7 +102,7 @@ suite('Test commands', () => { }); test('Select another branch', async () => { - const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as sinon.SinonStub<[string, any?], Thenable>; + const infoStub = sandbox.stub(vscode.window, 'showInformationMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'origin/master'); mockGit(); @@ -108,7 +114,7 @@ suite('Test commands', () => { }); test('Cannot get git information', async () => { - const warningStub = sandbox.stub(vscode.window, 'showWarningMessage') as sinon.SinonStub<[string, any?], Thenable>; + const warningStub = sandbox.stub(vscode.window, 'showWarningMessage') as unknown as sinon.SinonStub<[string], Thenable>; configureBranch(sandbox, 'origin/master'); mockGit(''); @@ -118,4 +124,51 @@ suite('Test commands', () => { sandbox.assert.calledWith(warningStub, 'Could not get Git info, please try a little later'); }); }); + + suite('copy-github-permalink.show', () => { + test('Show the file', async () => { + const showDocumentStub = sandbox.stub(vscode.window, 'showTextDocument') as unknown as sinon.SinonStub<[vscode.TextDocument, vscode.TextDocumentShowOptions]>; + + sandbox.stub(vscode.window, 'showInputBox').resolves('https://github.com/owner/name/blob/sha1234567890/test/fixtures/file.txt#L1-L3'); + + await vscode.commands.executeCommand('copy-github-permalink.show'); + + const document = await getFixtureFile('test/fixtures/file.txt'); + const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(3, 0)); + sandbox.assert.calledWith(showDocumentStub, document, { preview: false, selection: range }); + }); + + test('Show the file even without sha', async () => { + const showDocumentStub = sandbox.stub(vscode.window, 'showTextDocument') as unknown as sinon.SinonStub<[vscode.TextDocument, vscode.TextDocumentShowOptions]>; + + sandbox.stub(vscode.window, 'showInputBox').resolves('https://github.com/owner/name/blob/master/test/fixtures/file.txt'); + + await vscode.commands.executeCommand('copy-github-permalink.show'); + + const document = await getFixtureFile('test/fixtures/file.txt'); + sandbox.assert.calledWith(showDocumentStub, document, { preview: false }); + }); + + test('Show the file even without line', async () => { + const showDocumentStub = sandbox.stub(vscode.window, 'showTextDocument') as unknown as sinon.SinonStub<[vscode.TextDocument, vscode.TextDocumentShowOptions]>; + + sandbox.stub(vscode.window, 'showInputBox').resolves('https://github.com/owner/name/blob/sha1234567890/test/fixtures/file.txt'); + + await vscode.commands.executeCommand('copy-github-permalink.show'); + + const document = await getFixtureFile('test/fixtures/file.txt'); + sandbox.assert.calledWith(showDocumentStub, document, { preview: false }); + }); + + test('Show the file even with missing line', async () => { + const showDocumentStub = sandbox.stub(vscode.window, 'showTextDocument') as unknown as sinon.SinonStub<[vscode.TextDocument, vscode.TextDocumentShowOptions]>; + + sandbox.stub(vscode.window, 'showInputBox').resolves('https://github.com/owner/name/blob/sha1234567890/test/fixtures/file.txt#'); + + await vscode.commands.executeCommand('copy-github-permalink.show'); + + const document = await getFixtureFile('test/fixtures/file.txt'); + sandbox.assert.calledWith(showDocumentStub, document, { preview: false }); + }); + }); });