From a9a8957e27f5fb2e4cace9c7c281286e34732af9 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 6 Dec 2018 10:50:41 -0800 Subject: [PATCH 1/5] Add inline actions for file changes. --- package.json | 5 +++++ src/view/reviewManager.ts | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3360adb21..776505d2e 100644 --- a/package.json +++ b/package.json @@ -334,6 +334,11 @@ { "command": "pr.openFileInGitHub", "when": "view =~ /(pr|prStatus)/ && viewItem == filechange" + }, + { + "command": "review.openFile", + "group": "inline", + "when": "view == prStatus && viewItem == filechange" } ], "editor/title": [ diff --git a/src/view/reviewManager.ts b/src/view/reviewManager.ts index 6d8ac024d..b0ed9b693 100644 --- a/src/view/reviewManager.ts +++ b/src/view/reviewManager.ts @@ -7,7 +7,7 @@ import * as nodePath from 'path'; import * as vscode from 'vscode'; import { parseDiff, parsePatch } from '../common/diffHunk'; import { getDiffLineByPosition, getLastDiffLine, mapCommentsToHead, mapHeadLineToDiffHunkPosition, mapOldPositionToNew, getZeroBased, getAbsolutePosition } from '../common/diffPositionMapping'; -import { toReviewUri, fromReviewUri, fromPRUri } from '../common/uri'; +import { toReviewUri, fromReviewUri, fromPRUri, ReviewUriParams } from '../common/uri'; import { groupBy, formatError } from '../common/utils'; import { Comment } from '../common/comment'; import { GitChangeType, InMemFileChange } from '../common/file'; @@ -63,8 +63,16 @@ export class ReviewManager implements vscode.DecorationProvider { let gitContentProvider = new GitContentProvider(_repository); gitContentProvider.registerTextDocumentContentFallback(this.provideTextDocumentContent.bind(this)); this._disposables.push(vscode.workspace.registerTextDocumentContentProvider('review', gitContentProvider)); - this._disposables.push(vscode.commands.registerCommand('review.openFile', (uri: vscode.Uri) => { - let params = fromReviewUri(uri); + this._disposables.push(vscode.commands.registerCommand('review.openFile', (value: GitFileChangeNode | vscode.Uri) => { + let params: ReviewUriParams; + let filePath: string; + if (value instanceof GitFileChangeNode) { + params = fromReviewUri(value.filePath); + filePath = value.filePath.path; + } else { + params = fromReviewUri(value); + filePath = value.path; + } const activeTextEditor = vscode.window.activeTextEditor; const opts: vscode.TextDocumentShowOptions = { @@ -74,7 +82,7 @@ export class ReviewManager implements vscode.DecorationProvider { // Check if active text editor has same path as other editor. we cannot compare via // URI.toString() here because the schemas can be different. Instead we just go by path. - if (activeTextEditor && activeTextEditor.document.uri.path === uri.path) { + if (activeTextEditor && activeTextEditor.document.uri.path === filePath) { opts.selection = activeTextEditor.selection; } From b69beb8620fe51f3d4a84b1983ebb68c282cf13f Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 10 Dec 2018 15:04:21 -0800 Subject: [PATCH 2/5] Do not show open file action for deleted files --- package.json | 4 ++-- src/view/treeNodes/fileChangeNode.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 776505d2e..690331f7d 100644 --- a/package.json +++ b/package.json @@ -333,12 +333,12 @@ }, { "command": "pr.openFileInGitHub", - "when": "view =~ /(pr|prStatus)/ && viewItem == filechange" + "when": "view =~ /(pr|prStatus)/ && viewItem =~ /filechange/" }, { "command": "review.openFile", "group": "inline", - "when": "view == prStatus && viewItem == filechange" + "when": "view == prStatus && viewItem =~ /filechange(?!:DELETE)/" } ], "editor/title": [ diff --git a/src/view/treeNodes/fileChangeNode.ts b/src/view/treeNodes/fileChangeNode.ts index 56b7d4692..b10ce2570 100644 --- a/src/view/treeNodes/fileChangeNode.ts +++ b/src/view/treeNodes/fileChangeNode.ts @@ -146,7 +146,7 @@ export class GitFileChangeNode extends TreeNode implements vscode.TreeItem { public readonly sha?: string, ) { super(); - this.contextValue = 'filechange'; + this.contextValue = `filechange:${GitChangeType[status]}`; this.label = path.basename(fileName); this.description = path.relative('.', path.dirname(fileName)); this.iconPath = Resource.getFileStatusUri(this); From 1a2ba4cb400dc347fd240be6709baa4b81363c28 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 10 Dec 2018 16:10:10 -0800 Subject: [PATCH 3/5] Use file decorations for file change type. --- src/common/uri.ts | 13 +++-- src/extension.ts | 3 +- src/view/fileTypeDecorationProvider.ts | 80 ++++++++++++++++++++++++++ src/view/treeNodes/fileChangeNode.ts | 17 +++--- src/view/treeNodes/pullRequestNode.ts | 4 +- 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 src/view/fileTypeDecorationProvider.ts diff --git a/src/common/uri.ts b/src/common/uri.ts index 255982bce..7fbc2693d 100644 --- a/src/common/uri.ts +++ b/src/common/uri.ts @@ -7,6 +7,7 @@ import { Uri, UriHandler, EventEmitter } from 'vscode'; import { IPullRequestModel } from '../github/interface'; +import { GitChangeType } from './file'; export interface ReviewUriParams { path: string; @@ -26,6 +27,7 @@ export interface PRUriParams { isBase: boolean; fileName: string; prNumber: number; + status: GitChangeType; } export function fromPRUri(uri: Uri): PRUriParams { @@ -65,11 +67,13 @@ export function toReviewUri(uri: Uri, filePath: string, ref: string, commit: str export interface FileChangeNodeUriParams { hasComments?: boolean; + status?: GitChangeType; } -export function toFileChangeNodeUri(uri: Uri, hasComments: boolean) { +export function toFileChangeNodeUri(uri: Uri, hasComments: boolean, status: GitChangeType) { const params = { - hasComments: hasComments + hasComments: hasComments, + status: status }; return uri.with({ @@ -86,13 +90,14 @@ export function fromFileChangeNodeUri(uri: Uri): FileChangeNodeUriParams { } } -export function toPRUri(uri: Uri, pullRequestModel: IPullRequestModel, baseCommit: string, headCommit: string, fileName: string, base: boolean): Uri { +export function toPRUri(uri: Uri, pullRequestModel: IPullRequestModel, baseCommit: string, headCommit: string, fileName: string, base: boolean, status: GitChangeType): Uri { const params: PRUriParams = { baseCommit: baseCommit, headCommit: headCommit, isBase: base, fileName: fileName, - prNumber: pullRequestModel.prNumber + prNumber: pullRequestModel.prNumber, + status: status }; let path = uri.path; diff --git a/src/extension.ts b/src/extension.ts index b7f709cc7..7255ac9df 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,6 +17,7 @@ import { Telemetry } from './common/telemetry'; import { handler as uriHandler } from './common/uri'; import { ITelemetry } from './github/interface'; import * as Keychain from './authentication/keychain'; +import { FileTypeDecorationProvider } from './view/fileTypeDecorationProvider'; // fetch.promise polyfill const fetch = require('node-fetch'); @@ -45,7 +46,7 @@ async function init(context: vscode.ExtensionContext, git: GitAPI, repository: R })); context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); - + context.subscriptions.push(new FileTypeDecorationProvider()); const prManager = new PullRequestManager(repository, telemetry); const reviewManager = new ReviewManager(context, Keychain.onDidChange, repository, prManager, telemetry); registerCommands(context, prManager, reviewManager, telemetry); diff --git a/src/view/fileTypeDecorationProvider.ts b/src/view/fileTypeDecorationProvider.ts new file mode 100644 index 000000000..6fbdc5ad0 --- /dev/null +++ b/src/view/fileTypeDecorationProvider.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { fromFileChangeNodeUri, fromPRUri } from '../common/uri'; +import { GitChangeType } from '../common/file'; + +export class FileTypeDecorationProvider implements vscode.DecorationProvider { + private _disposables: vscode.Disposable[]; + + constructor( + ) { + this._disposables = []; + this._disposables.push(vscode.window.registerDecorationProvider(this)); + } + + _onDidChangeDecorations: vscode.EventEmitter = new vscode.EventEmitter(); + onDidChangeDecorations: vscode.Event = this._onDidChangeDecorations.event; + provideDecoration(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { + let fileChangeUriParams = fromFileChangeNodeUri(uri); + if (fileChangeUriParams && fileChangeUriParams.status !== undefined) { + return { + bubble: false, + letter: this.letter(fileChangeUriParams.status), + priority: this.priority(fileChangeUriParams.status) + }; + } + + let prParams = fromPRUri(uri); + + if (prParams && prParams.status !== undefined) { + return { + bubble: false, + letter: this.letter(prParams.status), + priority: this.priority(prParams.status) + }; + } + + return undefined; + } + + letter(status: GitChangeType): string { + switch (status) { + case GitChangeType.MODIFY: + return 'M'; + case GitChangeType.ADD: + return 'A'; + case GitChangeType.DELETE: + return 'D'; + case GitChangeType.RENAME: + return 'R'; + case GitChangeType.UNKNOWN: + return 'U'; + case GitChangeType.UNMERGED: + return 'C'; + } + + return ''; + } + + priority(status: GitChangeType): number { + switch (status) { + case GitChangeType.MODIFY: + case GitChangeType.RENAME: + return 2; + case GitChangeType.UNKNOWN: + return 3; + case GitChangeType.UNMERGED: + return 4; + default: + return 1; + } + } + + dispose() { + this._disposables.forEach(dispose => dispose.dispose()); + } +} diff --git a/src/view/treeNodes/fileChangeNode.ts b/src/view/treeNodes/fileChangeNode.ts index b10ce2570..70203ce98 100644 --- a/src/view/treeNodes/fileChangeNode.ts +++ b/src/view/treeNodes/fileChangeNode.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode'; import * as path from 'path'; import { DiffHunk, DiffChangeType } from '../../common/diffHunk'; import { GitChangeType } from '../../common/file'; -import { Resource } from '../../common/resources'; import { IPullRequestModel } from '../../github/interface'; import { TreeNode } from './treeNode'; import { Comment } from '../../common/comment'; @@ -20,7 +19,7 @@ import { toFileChangeNodeUri } from '../../common/uri'; export class RemoteFileChangeNode extends TreeNode implements vscode.TreeItem { public label: string; public description: string; - public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } | vscode.ThemeIcon; public command: vscode.Command; constructor( @@ -32,7 +31,7 @@ export class RemoteFileChangeNode extends TreeNode implements vscode.TreeItem { super(); this.label = path.basename(fileName); this.description = path.relative('.', path.dirname(fileName)); - this.iconPath = Resource.getFileStatusUri(this); + this.iconPath = vscode.ThemeIcon.File; this.command = { title: 'show remote file', @@ -54,7 +53,7 @@ export class RemoteFileChangeNode extends TreeNode implements vscode.TreeItem { export class InMemFileChangeNode extends TreeNode implements vscode.TreeItem { public label: string; public description: string; - public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } | vscode.ThemeIcon; public resourceUri: vscode.Uri; public parentSha: string; public contextValue: string; @@ -79,8 +78,8 @@ export class InMemFileChangeNode extends TreeNode implements vscode.TreeItem { this.contextValue = 'filechange'; this.label = path.basename(fileName); this.description = path.relative('.', path.dirname(fileName)); - this.iconPath = Resource.getFileStatusUri(this); - this.resourceUri = toFileChangeNodeUri(this.filePath, comments.length > 0); + this.iconPath = this.iconPath = vscode.ThemeIcon.File; + this.resourceUri = toFileChangeNodeUri(this.filePath, comments.length > 0, status); let opts: vscode.TextDocumentShowOptions = { preserveFocus: true @@ -127,7 +126,7 @@ export class InMemFileChangeNode extends TreeNode implements vscode.TreeItem { export class GitFileChangeNode extends TreeNode implements vscode.TreeItem { public label: string; public description: string; - public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } | vscode.ThemeIcon; public resourceUri: vscode.Uri; public parentSha: string; public contextValue: string; @@ -149,8 +148,8 @@ export class GitFileChangeNode extends TreeNode implements vscode.TreeItem { this.contextValue = `filechange:${GitChangeType[status]}`; this.label = path.basename(fileName); this.description = path.relative('.', path.dirname(fileName)); - this.iconPath = Resource.getFileStatusUri(this); - this.resourceUri = toFileChangeNodeUri(this.filePath, comments.length > 0); + this.iconPath = vscode.ThemeIcon.File; + this.resourceUri = toFileChangeNodeUri(this.filePath, comments.length > 0, status); let opts: vscode.TextDocumentShowOptions = { preserveFocus: true diff --git a/src/view/treeNodes/pullRequestNode.ts b/src/view/treeNodes/pullRequestNode.ts index 3b1eca8d3..9556891a8 100644 --- a/src/view/treeNodes/pullRequestNode.ts +++ b/src/view/treeNodes/pullRequestNode.ts @@ -254,8 +254,8 @@ export class PRNode extends TreeNode { change.fileName, change.previousFileName, change.blobUrl, - toPRUri(vscode.Uri.file(change.fileName), this.pullRequestModel, change.baseCommit, headCommit, change.fileName, false), - toPRUri(vscode.Uri.file(change.fileName), this.pullRequestModel, change.baseCommit, headCommit, change.fileName, true), + toPRUri(vscode.Uri.file(change.fileName), this.pullRequestModel, change.baseCommit, headCommit, change.fileName, false, change.status), + toPRUri(vscode.Uri.file(change.fileName), this.pullRequestModel, change.baseCommit, headCommit, change.fileName, true, change.status), change.isPartial, change.patch, change.diffHunks, From 898ddbe5082fe62c1ffeac400b6bc04e4bc5665b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 10 Dec 2018 16:17:39 -0800 Subject: [PATCH 4/5] Resource Uri for remote file. --- src/view/treeNodes/fileChangeNode.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/view/treeNodes/fileChangeNode.ts b/src/view/treeNodes/fileChangeNode.ts index 70203ce98..ac4c6544a 100644 --- a/src/view/treeNodes/fileChangeNode.ts +++ b/src/view/treeNodes/fileChangeNode.ts @@ -21,6 +21,7 @@ export class RemoteFileChangeNode extends TreeNode implements vscode.TreeItem { public description: string; public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri } | vscode.ThemeIcon; public command: vscode.Command; + public resourceUri: vscode.Uri; constructor( public readonly pullRequest: IPullRequestModel, @@ -32,6 +33,7 @@ export class RemoteFileChangeNode extends TreeNode implements vscode.TreeItem { this.label = path.basename(fileName); this.description = path.relative('.', path.dirname(fileName)); this.iconPath = vscode.ThemeIcon.File; + this.resourceUri = toFileChangeNodeUri(vscode.Uri.parse(this.blobUrl), false, status); this.command = { title: 'show remote file', From 06cad08dff22211c4bd38aa1d5ca3989c1bcd15b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 11 Dec 2018 11:07:06 -0800 Subject: [PATCH 5/5] Update priority. --- src/view/fileTypeDecorationProvider.ts | 18 ++---------------- src/view/prsTreeDataProvider.ts | 3 ++- src/view/reviewManager.ts | 3 ++- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/view/fileTypeDecorationProvider.ts b/src/view/fileTypeDecorationProvider.ts index 6fbdc5ad0..f250b8431 100644 --- a/src/view/fileTypeDecorationProvider.ts +++ b/src/view/fileTypeDecorationProvider.ts @@ -24,7 +24,7 @@ export class FileTypeDecorationProvider implements vscode.DecorationProvider { return { bubble: false, letter: this.letter(fileChangeUriParams.status), - priority: this.priority(fileChangeUriParams.status) + priority: 1 }; } @@ -34,7 +34,7 @@ export class FileTypeDecorationProvider implements vscode.DecorationProvider { return { bubble: false, letter: this.letter(prParams.status), - priority: this.priority(prParams.status) + priority: 1 }; } @@ -60,20 +60,6 @@ export class FileTypeDecorationProvider implements vscode.DecorationProvider { return ''; } - priority(status: GitChangeType): number { - switch (status) { - case GitChangeType.MODIFY: - case GitChangeType.RENAME: - return 2; - case GitChangeType.UNKNOWN: - return 3; - case GitChangeType.UNMERGED: - return 4; - default: - return 1; - } - } - dispose() { this._disposables.forEach(dispose => dispose.dispose()); } diff --git a/src/view/prsTreeDataProvider.ts b/src/view/prsTreeDataProvider.ts index 70d4af146..f77a61000 100644 --- a/src/view/prsTreeDataProvider.ts +++ b/src/view/prsTreeDataProvider.ts @@ -90,7 +90,8 @@ export class PullRequestsTreeDataProvider implements vscode.TreeDataProvider