Browse Source

Prettier all-the-things

main
Eric Amodio 6 years ago
parent
commit
d790e9db04
196 changed files with 6038 additions and 2978 deletions
  1. +2
    -0
      .prettierignore
  2. +16
    -0
      .prettierrc
  3. +15
    -0
      .vscode/settings.json
  4. +0
    -1
      BACKERS.md
  5. +10
    -10
      CODE_OF_CONDUCT.md
  6. +27
    -14
      package-lock.json
  7. +5
    -2
      package.json
  8. +86
    -38
      src/@types/applicationinsights/index.d.ts
  9. +33
    -11
      src/annotations/annotationProvider.ts
  10. +108
    -30
      src/annotations/annotations.ts
  11. +73
    -20
      src/annotations/blameAnnotationProvider.ts
  12. +104
    -30
      src/annotations/fileAnnotationController.ts
  13. +19
    -9
      src/annotations/gutterBlameAnnotationProvider.ts
  14. +1
    -2
      src/annotations/heatmapBlameAnnotationProvider.ts
  15. +1
    -1
      src/annotations/hoverBlameAnnotationProvider.ts
  16. +21
    -7
      src/annotations/lineAnnotationController.ts
  17. +56
    -12
      src/annotations/lineHoverController.ts
  18. +14
    -6
      src/annotations/recentChangesAnnotationProvider.ts
  19. +11
    -7
      src/codeLensController.ts
  20. +1
    -1
      src/commands.ts
  21. +1
    -2
      src/commands/clearFileAnnotations.ts
  22. +11
    -5
      src/commands/closeUnchangedFiles.ts
  23. +88
    -25
      src/commands/common.ts
  24. +13
    -7
      src/commands/copyMessageToClipboard.ts
  25. +9
    -7
      src/commands/copyShaToClipboard.ts
  26. +10
    -5
      src/commands/diffBranchWithBranch.ts
  27. +23
    -7
      src/commands/diffDirectory.ts
  28. +8
    -6
      src/commands/diffLineWithPrevious.ts
  29. +11
    -8
      src/commands/diffLineWithWorking.ts
  30. +27
    -14
      src/commands/diffWith.ts
  31. +6
    -4
      src/commands/diffWithBranch.ts
  32. +10
    -5
      src/commands/diffWithNext.ts
  33. +16
    -6
      src/commands/diffWithPrevious.ts
  34. +51
    -24
      src/commands/diffWithRevision.ts
  35. +12
    -4
      src/commands/diffWithWorking.ts
  36. +28
    -11
      src/commands/externalDiff.ts
  37. +22
    -7
      src/commands/openBranchInRemote.ts
  38. +18
    -6
      src/commands/openBranchesInRemote.ts
  39. +7
    -5
      src/commands/openChangedFiles.ts
  40. +14
    -10
      src/commands/openCommitInRemote.ts
  41. +30
    -10
      src/commands/openFileInRemote.ts
  42. +55
    -25
      src/commands/openFileRevision.ts
  43. +9
    -6
      src/commands/openInRemote.ts
  44. +18
    -6
      src/commands/openRepoInRemote.ts
  45. +5
    -3
      src/commands/openWorkingFile.ts
  46. +6
    -3
      src/commands/resetSuppressedWarnings.ts
  47. +53
    -29
      src/commands/showCommitSearch.ts
  48. +1
    -2
      src/commands/showGitExplorer.ts
  49. +1
    -2
      src/commands/showHistoryExplorer.ts
  50. +1
    -2
      src/commands/showLastQuickPick.ts
  51. +53
    -25
      src/commands/showQuickBranchHistory.ts
  52. +56
    -33
      src/commands/showQuickCommitDetails.ts
  53. +68
    -28
      src/commands/showQuickCommitFileDetails.ts
  54. +11
    -10
      src/commands/showQuickCurrentBranchHistory.ts
  55. +74
    -41
      src/commands/showQuickFileHistory.ts
  56. +6
    -3
      src/commands/showQuickRepoStatus.ts
  57. +27
    -16
      src/commands/showQuickStashList.ts
  58. +1
    -2
      src/commands/showResultsExplorer.ts
  59. +57
    -22
      src/commands/stashApply.ts
  60. +21
    -7
      src/commands/stashDelete.ts
  61. +12
    -5
      src/commands/stashSave.ts
  62. +0
    -3
      src/commands/switchMode.ts
  63. +0
    -1
      src/commands/toggleCodeLens.ts
  64. +9
    -4
      src/commands/toggleFileBlame.ts
  65. +4
    -3
      src/commands/toggleFileHeatmap.ts
  66. +4
    -3
      src/commands/toggleFileRecentChanges.ts
  67. +4
    -3
      src/commands/toggleLineBlame.ts
  68. +6
    -5
      src/comparers.ts
  69. +77
    -17
      src/configuration.ts
  70. +2
    -11
      src/constants.ts
  71. +24
    -21
      src/container.ts
  72. +356
    -173
      src/extension.ts
  73. +13
    -5
      src/git/formatters/commitFormatter.ts
  74. +16
    -6
      src/git/formatters/formatter.ts
  75. +12
    -5
      src/git/formatters/statusFormatter.ts
  76. +118
    -38
      src/git/git.ts
  77. +7
    -4
      src/git/gitLocator.ts
  78. +38
    -23
      src/git/gitUri.ts
  79. +1
    -1
      src/git/models/blame.ts
  80. +10
    -4
      src/git/models/blameCommit.ts
  81. +11
    -12
      src/git/models/branch.ts
  82. +28
    -10
      src/git/models/commit.ts
  83. +4
    -5
      src/git/models/diff.ts
  84. +2
    -2
      src/git/models/log.ts
  85. +17
    -9
      src/git/models/logCommit.ts
  86. +3
    -4
      src/git/models/remote.ts
  87. +36
    -15
      src/git/models/repository.ts
  88. +1
    -1
      src/git/models/stash.ts
  89. +13
    -3
      src/git/models/stashCommit.ts
  90. +40
    -20
      src/git/models/status.ts
  91. +3
    -6
      src/git/models/tag.ts
  92. +31
    -9
      src/git/parsers/blameParser.ts
  93. +2
    -6
      src/git/parsers/branchParser.ts
  94. +24
    -4
      src/git/parsers/diffParser.ts
  95. +49
    -12
      src/git/parsers/logParser.ts
  96. +10
    -4
      src/git/parsers/remoteParser.ts
  97. +12
    -6
      src/git/parsers/stashParser.ts
  98. +13
    -19
      src/git/parsers/statusParser.ts
  99. +2
    -3
      src/git/parsers/tagParser.ts
  100. +9
    -14
      src/git/remotes/bitbucket-server.ts

+ 2
- 0
.prettierignore View File

@ -0,0 +1,2 @@
package*.json
*.md

+ 16
- 0
.prettierrc View File

@ -0,0 +1,16 @@
{
"printWidth": 120,
"singleQuote": true,
"tabWidth": 4,
"useTabs": false,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
},
{
"files": "*.md",
"options": { "tabWidth": 2 }
}
]
}

+ 15
- 0
.vscode/settings.json View File

@ -21,5 +21,20 @@
"properties-order": "alphabetical",
"unspecified-properties-position": "bottom"
},
"tslint.autoFixOnSave": [
"curly",
"eofline",
"linebreak-style",
"trailing-comma",
"no-consecutive-blank-lines",
"no-irregular-whitespace",
"object-literal-key-quotes",
"one-line",
"ordered-imports",
"prefer-method-signature",
"prettiest",
"quotemark",
"whitespace"
],
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
}

+ 0
- 1
BACKERS.md View File

@ -14,7 +14,6 @@ None yet — could be you!
<h2 align="center">Silver Sponsors ($150+)</h2>
None yet &mdash; could be you!
<h2 align="center">Bronze Sponsors ($50+)</h2>
- Michael Duffy

+ 10
- 10
CODE_OF_CONDUCT.md View File

@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities

+ 27
- 14
package-lock.json View File

@ -767,14 +767,15 @@
}
},
"browserify-des": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz",
"integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
"integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
"dev": true,
"requires": {
"cipher-base": "^1.0.1",
"des.js": "^1.0.0",
"inherits": "^2.0.1"
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"browserify-rsa": {
@ -2499,9 +2500,9 @@
}
},
"get-caller-file": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
"dev": true
},
"get-stream": {
@ -3114,13 +3115,13 @@
}
},
"hash.js": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.4.tgz",
"integrity": "sha512-A6RlQvvZEtFS5fLU43IDu0QUmBy+fDO9VMdTXvufKwIkt/rFfvICAViCax5fbDO4zdNzaC3/27ZhKUok5bAJyw==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
"integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.0"
"minimalistic-assert": "^1.0.1"
}
},
"he": {
@ -4461,6 +4462,12 @@
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
"dev": true
},
"prettier": {
"version": "1.13.7",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz",
"integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==",
"dev": true
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -5483,6 +5490,12 @@
"tsutils": "^2.12.1"
}
},
"tslint-prettiest": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/tslint-prettiest/-/tslint-prettiest-0.0.1.tgz",
"integrity": "sha512-zFmqDWCgHIswGksTPLplSSc/U2Y7HKZWZiJAOuyOLO1Oer0nPl0itEUa1hdu5LOeRShGHXywXML9SbYCOu1TqA==",
"dev": true
},
"tsutils": {
"version": "2.27.2",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.2.tgz",
@ -5892,9 +5905,9 @@
}
},
"webpack": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.15.1.tgz",
"integrity": "sha512-UwfFQ2plA5EMhhzwi/hl5xpLk7mNK7p0853Ml04z1Bqw553pY+oS8Xke3funcVy7eG/yMpZPvnlFTUyGKyKoyw==",
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.0.tgz",
"integrity": "sha512-oNx9djAd6uAcccyfqN3hyXLNMjZHiRySZmBQ4c8FNmf1SNJGhx7n9TSvHNyXxgToRdH65g/Q97s94Ip9N6F7xg==",
"dev": true,
"requires": {
"@webassemblyjs/ast": "1.5.13",

+ 5
- 2
package.json View File

@ -3348,8 +3348,9 @@
"build-ui": "webpack --context ./src/ui --config ./src/ui/webpack.config.js",
"bundle": "npm run lint && webpack --env.production --context ./src/ui --config ./src/ui/webpack.config.js && webpack --env.production",
"clean": "git clean -Xdf",
"lint": "tslint --project tsconfig.json",
"lint": "tslint --project tsconfig.json --fix",
"pack": "vsce package",
"pretty": "prettier --config .prettierrc --loglevel warn --write \"./**/*.ts\"",
"pub": "vsce publish",
"rebuild": "npm run reset && npm run lint && tsc -m commonjs -p ./ && npm run build-ui",
"reset": "npm run clean && npm install --no-save",
@ -3373,13 +3374,15 @@
"@types/node": "7.0.67",
"@types/tmp": "0.0.33",
"husky": "0.14.3",
"prettier": "1.13.7",
"ts-loader": "4.4.2",
"tslint": "5.10.0",
"tslint-prettiest": "0.0.1",
"typescript": "2.9.2",
"uglify-es": "3.3.9",
"uglifyjs-webpack-plugin": "1.2.7",
"vscode": "1.1.18",
"webpack": "4.15.1",
"webpack": "4.16.0",
"webpack-cli": "3.0.8",
"webpack-node-externals": "1.7.2"
}

+ 86
- 38
src/@types/applicationinsights/index.d.ts View File

@ -33,32 +33,31 @@ interface AutoCollectRequests {
isInitialized(): boolean;
}
declare namespace ContractsModule {
enum DataPointType {
Measurement = 0,
Aggregation = 1,
Aggregation = 1
}
enum DependencyKind {
SQL = 0,
Http = 1,
Other = 2,
Other = 2
}
enum DependencySourceType {
Undefined = 0,
Aic = 1,
Apmc = 2,
Apmc = 2
}
enum SessionState {
Start = 0,
End = 1,
End = 1
}
enum SeverityLevel {
Verbose = 0,
Information = 1,
Warning = 2,
Error = 3,
Critical = 4,
Critical = 4
}
interface ContextTagKeys {
applicationVersion: string;
@ -279,9 +278,13 @@ declare namespace ContractsModule {
}
}
interface Channel {
constructor(isDisabled: () => boolean, getBatchSize: () => number, getBatchIntervalMs: () => number, sender: Sender): Channel;
constructor(
isDisabled: () => boolean,
getBatchSize: () => number,
getBatchIntervalMs: () => number,
sender: Sender
): Channel;
/**
* Add a telemetry item to the send buffer
*/
@ -315,28 +318,39 @@ interface Client {
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
trackEvent(name: string, properties?: {
[key: string]: string;
}, measurements?: {
[key: string]: number;
}): void;
trackEvent(
name: string,
properties?: {
[key: string]: string;
},
measurements?: {
[key: string]: number;
}
): void;
/**
* Log a trace message
* @param message A string to identify this event in the portal.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
*/
trackTrace(message: string, severityLevel?: ContractsModule.SeverityLevel, properties?: {
[key: string]: string;
}): void;
trackTrace(
message: string,
severityLevel?: ContractsModule.SeverityLevel,
properties?: {
[key: string]: string;
}
): void;
/**
* Log an exception you have caught.
* @param exception An Error from a catch clause, or the string error message.
* @param properties map[string, string] - additional data used to filter events and metrics in the portal. Defaults to empty.
* @param measurements map[string, number] - metrics associated with this event, displayed in Metrics Explorer on the portal. Defaults to empty.
*/
trackException(exception: Error, properties?: {
[key: string]: string;
}): void;
trackException(
exception: Error,
properties?: {
[key: string]: string;
}
): void;
/**
* Log a numeric value that is not associated with a specific event. Typically used to send regular reports of performance indicators.
* To send a single measurement, use just the first two parameters. If you take measurements very frequently, you can reduce the
@ -348,9 +362,17 @@ interface Client {
* @param max the max sample for this set
* @param stdDev the standard deviation of the set
*/
trackMetric(name: string, value: number, count?: number, min?: number, max?: number, stdDev?: number, properties?: {
[key: string]: string;
}): void;
trackMetric(
name: string,
value: number,
count?: number,
min?: number,
max?: number,
stdDev?: number,
properties?: {
[key: string]: string;
}
): void;
/**
* Log an incoming http request to your server. The request data will be tracked during the response "finish" event if it is successful or the request "error"
@ -360,9 +382,13 @@ interface Client {
* @param response The http.ServerResponse object for this request
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
*/
trackRequest(request: any /* http.IncomingMessage */, response: any /* http.ServerResponse */, properties?: {
[key: string]: string;
}): void;
trackRequest(
request: any /* http.IncomingMessage */,
response: any /* http.ServerResponse */,
properties?: {
[key: string]: string;
}
): void;
/**
* Log an incoming http request to your server. The request data is tracked synchronously rather than waiting for the response "finish"" or request "error"" events.
@ -373,9 +399,15 @@ interface Client {
* @param properties map[string, string] - additional data used to filter requests in the portal. Defaults to empty.
* @param error An error that was returned for this request if it was unsuccessful. Defaults to null.
*/
trackRequestSync(request: any /*http.IncomingMessage */, response: any /*http.ServerResponse */, ellapsedMilliseconds?: number, properties?: {
[key: string]: string;
}, error?: any): void;
trackRequestSync(
request: any /*http.IncomingMessage */,
response: any /*http.ServerResponse */,
ellapsedMilliseconds?: number,
properties?: {
[key: string]: string;
},
error?: any
): void;
/**
* Log information about a dependency of your app. Typically used to track the time database calls or outgoing http requests take from your server.
@ -389,22 +421,38 @@ interface Client {
* @param async True if the dependency was executed asynchronously, false otherwise. Defaults to false
* @param dependencySource ContractsModule.DependencySourceType of this dependency. Defaults to Undefined.
*/
trackDependency(name: string, commandName: string, elapsedTimeMs: number, success: boolean, dependencyTypeName?: string, properties?: {}, dependencyKind?: any, async?: boolean, dependencySource?: number): void;
trackDependency(
name: string,
commandName: string,
elapsedTimeMs: number,
success: boolean,
dependencyTypeName?: string,
properties?: {},
dependencyKind?: any,
async?: boolean,
dependencySource?: number
): void;
/**
* Immediately send all queued telemetry.
*/
sendPendingData(callback?: (response: string) => void): void;
getEnvelope(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
[key: string]: string;
}): ContractsModule.Envelope;
getEnvelope(
data: ContractsModule.Data<ContractsModule.Domain>,
tagOverrides?: {
[key: string]: string;
}
): ContractsModule.Envelope;
/**
* Generic track method for all telemetry types
* @param data the telemetry to send
* @param tagOverrides the context tags to use for this telemetry which overwrite default context values
*/
track(data: ContractsModule.Data<ContractsModule.Domain>, tagOverrides?: {
[key: string]: string;
}): void;
track(
data: ContractsModule.Data<ContractsModule.Domain>,
tagOverrides?: {
[key: string]: string;
}
): void;
}
interface Config {
@ -428,7 +476,7 @@ interface Context {
interface Sender {
constructor(getUrl: () => string, onSuccess?: (response: string) => void, onError?: (error: Error) => void): Sender;
send(payload: any/* Buffer */): void;
send(payload: any /* Buffer */): void;
saveOnCrash(payload: string): void;
/**
* enable caching events locally on error
@ -511,7 +559,7 @@ interface ApplicationInsights {
setAutoDependencyCorrelation(value: boolean): ApplicationInsights;
}
declare module "applicationinsights"; {
declare module 'applicationinsights'; {
const applicationinsights: ApplicationInsights;
export = applicationinsights;
}
}

+ 33
- 11
src/annotations/annotationProvider.ts View File

@ -1,6 +1,16 @@
'use strict';
import { Functions } from '../system';
import { DecorationOptions, Disposable, Range, TextDocument, TextEditor, TextEditorDecorationType, TextEditorSelectionChangeEvent, Uri, window } from 'vscode';
import {
DecorationOptions,
Disposable,
Range,
TextDocument,
TextEditor,
TextEditorDecorationType,
TextEditorSelectionChangeEvent,
Uri,
window
} from 'vscode';
import { FileAnnotationType } from '../configuration';
import { TextDocumentComparer } from '../comparers';
import { CommandContext, setCommandContext } from '../constants';
@ -14,7 +24,6 @@ export enum AnnotationStatus {
export type TextEditorCorrelationKey = string;
export abstract class AnnotationProviderBase extends Disposable {
static getCorrelationKey(editor: TextEditor | undefined): TextEditorCorrelationKey {
return editor !== undefined ? (editor as any).id : '';
}
@ -65,7 +74,7 @@ export abstract class AnnotationProviderBase extends Disposable {
return this.editor.document.uri;
}
protected additionalDecorations: { decoration: TextEditorDecorationType, ranges: Range[] }[] | undefined;
protected additionalDecorations: { decoration: TextEditorDecorationType; ranges: Range[] }[] | undefined;
async clear() {
this.status = undefined;
@ -75,7 +84,7 @@ export abstract class AnnotationProviderBase extends Disposable {
try {
this.editor.setDecorations(this.decoration, []);
}
catch { }
catch {}
}
if (this.additionalDecorations !== undefined && this.additionalDecorations.length > 0) {
@ -83,7 +92,7 @@ export abstract class AnnotationProviderBase extends Disposable {
try {
this.editor.setDecorations(d.decoration, []);
}
catch { }
catch {}
}
this.additionalDecorations = undefined;
@ -93,13 +102,23 @@ export abstract class AnnotationProviderBase extends Disposable {
try {
this.editor.setDecorations(this.highlightDecoration, []);
}
catch { }
catch {}
}
}
private _resetDebounced: ((changes?: { decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined }) => Promise<void>) | undefined;
async reset(changes?: { decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined }) {
private _resetDebounced:
| ((
changes?: {
decoration: TextEditorDecorationType;
highlightDecoration: TextEditorDecorationType | undefined;
}
) => Promise<void>)
| undefined;
async reset(changes?: {
decoration: TextEditorDecorationType;
highlightDecoration: TextEditorDecorationType | undefined;
}) {
if (this._resetDebounced === undefined) {
this._resetDebounced = Functions.debounce(this.onReset, 250);
}
@ -107,7 +126,10 @@ export abstract class AnnotationProviderBase extends Disposable {
this._resetDebounced(changes);
}
async onReset(changes?: { decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined }) {
async onReset(changes?: {
decoration: TextEditorDecorationType;
highlightDecoration: TextEditorDecorationType | undefined;
}) {
if (changes !== undefined) {
await this.clear();
@ -163,4 +185,4 @@ export abstract class AnnotationProviderBase extends Disposable {
abstract async onProvideAnnotation(shaOrLine?: string | number): Promise<boolean>;
abstract async selection(shaOrLine?: string | number): Promise<void>;
abstract async validate(): Promise<boolean>;
}
}

+ 108
- 30
src/annotations/annotations.ts View File

@ -1,15 +1,35 @@
import { Objects, Strings } from '../system';
import { DecorationInstanceRenderOptions, DecorationOptions, MarkdownString, ThemableDecorationRenderOptions, ThemeColor } from 'vscode';
import { DiffWithCommand, OpenCommitInRemoteCommand, OpenFileRevisionCommand, ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand } from '../commands';
import {
DecorationInstanceRenderOptions,
DecorationOptions,
MarkdownString,
ThemableDecorationRenderOptions,
ThemeColor
} from 'vscode';
import {
DiffWithCommand,
OpenCommitInRemoteCommand,
OpenFileRevisionCommand,
ShowQuickCommitDetailsCommand,
ShowQuickCommitFileDetailsCommand
} from '../commands';
import { FileAnnotationType } from './../configuration';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { CommitFormatter, GitCommit, GitDiffChunkLine, GitRemote, GitService, GitUri, ICommitFormatOptions } from '../gitService';
import {
CommitFormatter,
GitCommit,
GitDiffChunkLine,
GitRemote,
GitService,
GitUri,
ICommitFormatOptions
} from '../gitService';
import { toRgba } from '../ui/shared/colors';
export interface ComputedHeatmap {
cold: boolean;
colors: { hot: string, cold: string };
colors: { hot: string; cold: string };
median: number;
newest: number;
oldest: number;
@ -32,21 +52,18 @@ const escapeMarkdownRegEx = /[`\>\#\*\_\-\+\.]/g;
// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___';
let computedHeatmapColor: {
color: string,
rgb: string
color: string;
rgb: string;
};
export class Annotations {
static applyHeatmap(decoration: DecorationOptions, date: Date, heatmap: ComputedHeatmap) {
const color = this.getHeatmapColor(date, heatmap);
(decoration.renderOptions!.before! as any).borderColor = color;
}
private static getHeatmapColor(date: Date, heatmap: ComputedHeatmap) {
const baseColor = heatmap.cold
? heatmap.colors.cold
: heatmap.colors.hot;
const baseColor = heatmap.cold ? heatmap.colors.cold : heatmap.colors.hot;
const age = heatmap.computeAge(date);
if (age === 0) return baseColor;
@ -64,11 +81,18 @@ export class Annotations {
};
}
return `rgba(${computedHeatmapColor.rgb}, ${(1 - (age / 10)).toFixed(2)})`;
return `rgba(${computedHeatmapColor.rgb}, ${(1 - age / 10).toFixed(2)})`;
}
private static getHoverCommandBar(commit: GitCommit, hasRemote: boolean, annotationType?: FileAnnotationType, line: number = 0) {
let commandBar = `[\`${GlyphChars.MuchGreaterThan}\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") `;
private static getHoverCommandBar(
commit: GitCommit,
hasRemote: boolean,
annotationType?: FileAnnotationType,
line: number = 0
) {
let commandBar = `[\`${GlyphChars.MuchGreaterThan}\`](${DiffWithCommand.getMarkdownCommandArgs(
commit
)} "Open Changes") `;
if (commit.previousSha !== undefined) {
if (annotationType === FileAnnotationType.RecentChanges) {
@ -76,19 +100,33 @@ export class Annotations {
}
const uri = GitUri.toRevisionUri(commit.previousSha, commit.previousUri.fsPath, commit.repoPath);
commandBar += `[\`${GlyphChars.SquareWithTopShadow}\`](${OpenFileRevisionCommand.getMarkdownCommandArgs(uri, annotationType || FileAnnotationType.Blame, line)} "Blame Previous Revision") `;
commandBar += `[\`${GlyphChars.SquareWithTopShadow}\`](${OpenFileRevisionCommand.getMarkdownCommandArgs(
uri,
annotationType || FileAnnotationType.Blame,
line
)} "Blame Previous Revision") `;
}
if (hasRemote) {
commandBar += `[\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(commit.sha)} "Open in Remote") `;
commandBar += `[\`${GlyphChars.ArrowUpRight}\`](${OpenCommitInRemoteCommand.getMarkdownCommandArgs(
commit.sha
)} "Open in Remote") `;
}
commandBar += `[\`${GlyphChars.MiddleEllipsis}\`](${ShowQuickCommitFileDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show More Actions")`;
commandBar += `[\`${GlyphChars.MiddleEllipsis}\`](${ShowQuickCommitFileDetailsCommand.getMarkdownCommandArgs(
commit.sha
)} "Show More Actions")`;
return commandBar;
}
static getHoverMessage(commit: GitCommit, dateFormat: string | null, remotes: GitRemote[], annotationType?: FileAnnotationType, line: number = 0): MarkdownString {
static getHoverMessage(
commit: GitCommit,
dateFormat: string | null,
remotes: GitRemote[],
annotationType?: FileAnnotationType,
line: number = 0
): MarkdownString {
if (dateFormat === null) {
dateFormat = 'MMMM Do, YYYY h:mma';
}
@ -99,7 +137,9 @@ export class Annotations {
let avatar = '';
if (!commit.isUncommitted) {
commandBar = `\n\n${this.getHoverCommandBar(commit, remotes.length !== 0, annotationType, line)}`;
showCommitDetailsCommand = `[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")`;
showCommitDetailsCommand = `[\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.sha
)} "Show Commit Details")`;
message = commit.message;
for (const r of remotes) {
@ -126,12 +166,20 @@ export class Annotations {
avatar = ` &nbsp; ![](${commit.getGravatarUri(Container.config.defaultGravatarsStyle).toString()})`;
}
const markdown = new MarkdownString(`${showCommitDetailsCommand}${avatar} &nbsp;__${commit.author}__, ${commit.fromNow()} &nbsp; _(${commit.formatDate(dateFormat)})_ ${message}${commandBar}`);
const markdown = new MarkdownString(
`${showCommitDetailsCommand}${avatar} &nbsp;__${
commit.author
}__, ${commit.fromNow()} &nbsp; _(${commit.formatDate(dateFormat)})_ ${message}${commandBar}`
);
markdown.isTrusted = true;
return markdown;
}
static getHoverDiffMessage(commit: GitCommit, uri: GitUri, chunkLine: GitDiffChunkLine | undefined): MarkdownString | undefined {
static getHoverDiffMessage(
commit: GitCommit,
uri: GitUri,
chunkLine: GitDiffChunkLine | undefined
): MarkdownString | undefined {
if (chunkLine === undefined || commit.previousSha === undefined) return undefined;
const codeDiff = this.getCodeDiff(chunkLine);
@ -139,14 +187,28 @@ export class Annotations {
let message: string;
if (commit.isUncommitted) {
if (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha)) {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${GlyphChars.Dash} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} _${uri.shortSha}_\n${codeDiff}`;
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${
GlyphChars.Dash
} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.previousSha!
)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} _${uri.shortSha}_\n${codeDiff}`;
}
else {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${GlyphChars.Dash} &nbsp; _uncommitted changes_\n${codeDiff}`;
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${
GlyphChars.Dash
} &nbsp; _uncommitted changes_\n${codeDiff}`;
}
}
else {
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${GlyphChars.Dash} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.previousSha!)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} [\`${commit.shortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(commit.sha)} "Show Commit Details")\n${codeDiff}`;
message = `[\`Changes\`](${DiffWithCommand.getMarkdownCommandArgs(commit)} "Open Changes") &nbsp; ${
GlyphChars.Dash
} &nbsp; [\`${commit.previousShortSha}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.previousSha!
)} "Show Commit Details") ${GlyphChars.ArrowLeftRightLong} [\`${
commit.shortSha
}\`](${ShowQuickCommitDetailsCommand.getMarkdownCommandArgs(
commit.sha
)} "Show Commit Details")\n${codeDiff}`;
}
const markdown = new MarkdownString(message);
@ -163,9 +225,10 @@ export class Annotations {
}
static async changesHover(commit: GitCommit, line: number, uri: GitUri): Promise<DecorationOptions> {
const sha = !commit.isUncommitted || (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha))
? commit.previousSha
: undefined;
const sha =
!commit.isUncommitted || (uri.sha !== undefined && GitService.isStagedUncommitted(uri.sha))
? commit.previousSha
: undefined;
const chunkLine = await Container.git.getDiffForLine(uri, line, sha);
const message = this.getHoverDiffMessage(commit, uri, chunkLine);
@ -181,7 +244,12 @@ export class Annotations {
// } as DecorationOptions;
// }
static gutter(commit: GitCommit, format: string, dateFormatOrFormatOptions: string | null | ICommitFormatOptions, renderOptions: IRenderOptions): DecorationOptions {
static gutter(
commit: GitCommit,
format: string,
dateFormatOrFormatOptions: string | null | ICommitFormatOptions,
renderOptions: IRenderOptions
): DecorationOptions {
const decoration = {
renderOptions: {
before: { ...renderOptions }
@ -198,7 +266,12 @@ export class Annotations {
return decoration;
}
static gutterRenderOptions(separateLines: boolean, heatmap: IHeatmapConfig, format: string, options: ICommitFormatOptions): IRenderOptions {
static gutterRenderOptions(
separateLines: boolean,
heatmap: IHeatmapConfig,
format: string,
options: ICommitFormatOptions
): IRenderOptions {
// Get the width of all the tokens, assuming there there is a cap (bail if not)
let width = 0;
for (const token of Objects.values(options.tokenOptions!)) {
@ -289,7 +362,12 @@ export class Annotations {
// } as IRenderOptions;
// }
static trailing(commit: GitCommit, format: string, dateFormat: string | null, scrollable: boolean = true): DecorationOptions {
static trailing(
commit: GitCommit,
format: string,
dateFormat: string | null,
scrollable: boolean = true
): DecorationOptions {
const message = CommitFormatter.fromTemplate(format, commit, {
truncateMessageAtNewLine: true,
dateFormat: dateFormat
@ -326,4 +404,4 @@ export class Annotations {
// return { ...decoration, range: range };
// }
}
}

+ 73
- 20
src/annotations/blameAnnotationProvider.ts View File

@ -1,6 +1,17 @@
'use strict';
import { Arrays, Iterables } from '../system';
import { CancellationToken, Disposable, Hover, HoverProvider, languages, Position, Range, TextDocument, TextEditor, TextEditorDecorationType } from 'vscode';
import {
CancellationToken,
Disposable,
Hover,
HoverProvider,
languages,
Position,
Range,
TextDocument,
TextEditor,
TextEditorDecorationType
} from 'vscode';
import { AnnotationProviderBase } from './annotationProvider';
import { Annotations, ComputedHeatmap } from './annotations';
import { Container } from '../container';
@ -8,7 +19,6 @@ import { GitDocumentState, TrackedDocument } from '../trackers/gitDocumentTracke
import { GitBlame, GitCommit, GitUri } from '../gitService';
export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase {
protected _blame: Promise<GitBlame | undefined>;
protected _hoverProviderDisposable: Disposable | undefined;
protected readonly _uri: GitUri;
@ -36,7 +46,10 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
super.clear();
}
async onReset(changes?: { decoration: TextEditorDecorationType, highlightDecoration: TextEditorDecorationType | undefined }) {
async onReset(changes?: {
decoration: TextEditorDecorationType;
highlightDecoration: TextEditorDecorationType | undefined;
}) {
if (this.editor !== undefined) {
this._blame = this.editor.document.isDirty
? Container.git.getBlameForFileContents(this._uri, this.editor.document.getText())
@ -73,8 +86,13 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
return;
}
const highlightDecorationRanges = Arrays.filterMap(blame.lines,
l => l.sha === sha ? this.editor.document.validateRange(new Range(l.line, 0, l.line, Number.MAX_SAFE_INTEGER)) : undefined);
const highlightDecorationRanges = Arrays.filterMap(
blame.lines,
l =>
l.sha === sha
? this.editor.document.validateRange(new Range(l.line, 0, l.line, Number.MAX_SAFE_INTEGER))
: undefined
);
this.editor.setDecorations(this.highlightDecoration, highlightDecorationRanges);
}
@ -109,16 +127,15 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
dates.sort((a, b) => a.getTime() - b.getTime());
const half = Math.floor(dates.length / 2);
const median = dates.length % 2
? dates[half].getTime()
: (dates[half - 1].getTime() + dates[half].getTime()) / 2.0;
const median =
dates.length % 2 ? dates[half].getTime() : (dates[half - 1].getTime() + dates[half].getTime()) / 2.0;
const lookup: number[] = [];
const newest = dates[dates.length - 1].getTime();
let step = (newest - median) / 5;
for (let i = 5; i > 0; i--) {
lookup.push(median + (step * i));
lookup.push(median + step * i);
}
lookup.push(median);
@ -126,7 +143,7 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
const oldest = dates[0].getTime();
step = (median - oldest) / 4;
for (let i = 1; i <= 4; i++) {
lookup.push(median - (step * i));
lookup.push(median - step * i);
}
const d = new Date();
@ -154,28 +171,48 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
};
}
registerHoverProviders(providers: { details: boolean, changes: boolean }) {
if (!Container.config.hovers.enabled || !Container.config.hovers.annotations.enabled || (!providers.details && !providers.changes)) return;
registerHoverProviders(providers: { details: boolean; changes: boolean }) {
if (
!Container.config.hovers.enabled ||
!Container.config.hovers.annotations.enabled ||
(!providers.details && !providers.changes)
) {
return;
}
const subscriptions: Disposable[] = [];
if (providers.changes) {
subscriptions.push(languages.registerHoverProvider({ pattern: this.document.uri.fsPath }, { provideHover: this.provideChangesHover.bind(this) } as HoverProvider));
subscriptions.push(
languages.registerHoverProvider({ pattern: this.document.uri.fsPath }, {
provideHover: this.provideChangesHover.bind(this)
} as HoverProvider)
);
}
if (providers.details) {
subscriptions.push(languages.registerHoverProvider({ pattern: this.document.uri.fsPath }, { provideHover: this.provideDetailsHover.bind(this) } as HoverProvider));
subscriptions.push(
languages.registerHoverProvider({ pattern: this.document.uri.fsPath }, {
provideHover: this.provideDetailsHover.bind(this)
} as HoverProvider)
);
}
this._hoverProviderDisposable = Disposable.from(...subscriptions);
}
async provideDetailsHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
async provideDetailsHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
const commit = await this.getCommitForHover(position);
if (commit === undefined) return undefined;
// Get the full commit message -- since blame only returns the summary
let logCommit: GitCommit | undefined = undefined;
if (!commit.isUncommitted) {
logCommit = await Container.git.getLogCommitForFile(commit.repoPath, commit.uri.fsPath, { ref: commit.sha });
logCommit = await Container.git.getLogCommitForFile(commit.repoPath, commit.uri.fsPath, {
ref: commit.sha
});
if (logCommit !== undefined) {
// Preserve the previous commit from the blame commit
logCommit.previousFileName = commit.previousFileName;
@ -183,18 +220,34 @@ export abstract class BlameAnnotationProviderBase extends AnnotationProviderBase
}
}
const message = Annotations.getHoverMessage(logCommit || commit, Container.config.defaultDateFormat, await Container.git.getRemotes(commit.repoPath), this.annotationType, this.editor.selection.active.line);
return new Hover(message, document.validateRange(new Range(position.line, 0, position.line, Number.MAX_SAFE_INTEGER)));
const message = Annotations.getHoverMessage(
logCommit || commit,
Container.config.defaultDateFormat,
await Container.git.getRemotes(commit.repoPath),
this.annotationType,
this.editor.selection.active.line
);
return new Hover(
message,
document.validateRange(new Range(position.line, 0, position.line, Number.MAX_SAFE_INTEGER))
);
}
async provideChangesHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
async provideChangesHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
const commit = await this.getCommitForHover(position);
if (commit === undefined) return undefined;
const hover = await Annotations.changesHover(commit, position.line, await GitUri.fromUri(document.uri));
if (hover.hoverMessage === undefined) return undefined;
return new Hover(hover.hoverMessage, document.validateRange(new Range(position.line, 0, position.line, Number.MAX_SAFE_INTEGER)));
return new Hover(
hover.hoverMessage,
document.validateRange(new Range(position.line, 0, position.line, Number.MAX_SAFE_INTEGER))
);
}
private async getCommitForHover(position: Position): Promise<GitCommit | undefined> {

+ 104
- 30
src/annotations/fileAnnotationController.ts View File

@ -1,11 +1,32 @@
'use strict';
import { Functions, Iterables } from '../system';
import { ConfigurationChangeEvent, DecorationRangeBehavior, DecorationRenderOptions, Disposable, Event, EventEmitter, OverviewRulerLane, Progress, ProgressLocation, TextDocument, TextEditor, TextEditorDecorationType, TextEditorViewColumnChangeEvent, ThemeColor, window, workspace } from 'vscode';
import {
ConfigurationChangeEvent,
DecorationRangeBehavior,
DecorationRenderOptions,
Disposable,
Event,
EventEmitter,
OverviewRulerLane,
Progress,
ProgressLocation,
TextDocument,
TextEditor,
TextEditorDecorationType,
TextEditorViewColumnChangeEvent,
ThemeColor,
window,
workspace
} from 'vscode';
import { AnnotationProviderBase, AnnotationStatus, TextEditorCorrelationKey } from './annotationProvider';
import { AnnotationsToggleMode, configuration, FileAnnotationType, HighlightLocations } from '../configuration';
import { CommandContext, isTextEditor, setCommandContext } from '../constants';
import { Container } from '../container';
import { DocumentBlameStateChangeEvent, DocumentDirtyStateChangeEvent, GitDocumentState } from '../trackers/gitDocumentTracker';
import {
DocumentBlameStateChangeEvent,
DocumentDirtyStateChangeEvent,
GitDocumentState
} from '../trackers/gitDocumentTracker';
import { GutterBlameAnnotationProvider } from './gutterBlameAnnotationProvider';
import { HeatmapBlameAnnotationProvider } from './heatmapBlameAnnotationProvider';
import { KeyboardScope, KeyCommand, Keys } from '../keyboard';
@ -35,7 +56,6 @@ export const Decorations = {
};
export class FileAnnotationController extends Disposable {
private _onDidToggleAnnotations = new EventEmitter<void>();
get onDidToggleAnnotations(): Event<void> {
return this._onDidToggleAnnotations.event;
@ -52,9 +72,7 @@ export class FileAnnotationController extends Disposable {
constructor() {
super(() => this.dispose());
this._disposable = Disposable.from(
configuration.onDidChange(this.onConfigurationChanged, this)
);
this._disposable = Disposable.from(configuration.onDidChange(this.onConfigurationChanged, this));
this._toggleModes = new Map();
this.onConfigurationChanged(configuration.initializingChangeEvent);
@ -159,19 +177,27 @@ export class FileAnnotationController extends Disposable {
if (initializing) return;
if (configuration.changed(e, configuration.name('blame').value) ||
if (
configuration.changed(e, configuration.name('blame').value) ||
configuration.changed(e, configuration.name('recentChanges').value) ||
configuration.changed(e, configuration.name('heatmap').value) ||
configuration.changed(e, configuration.name('hovers').value)) {
configuration.changed(e, configuration.name('hovers').value)
) {
// Since the configuration has changed -- reset any visible annotations
for (const provider of this._annotationProviders.values()) {
if (provider === undefined) continue;
if (provider.annotationType === FileAnnotationType.RecentChanges) {
provider.reset({ decoration: Decorations.recentChangesAnnotation!, highlightDecoration: Decorations.recentChangesHighlight });
provider.reset({
decoration: Decorations.recentChangesAnnotation!,
highlightDecoration: Decorations.recentChangesHighlight
});
}
else if (provider.annotationType === FileAnnotationType.Blame) {
provider.reset({ decoration: Decorations.blameAnnotation, highlightDecoration: Decorations.blameHighlight });
provider.reset({
decoration: Decorations.blameAnnotation,
highlightDecoration: Decorations.blameHighlight
});
}
else {
this.show(provider.editor, FileAnnotationType.Heatmap);
@ -236,7 +262,10 @@ export class FileAnnotationController extends Disposable {
const provider = this.getProvider(e.textEditor);
if (provider === undefined) {
// If we don't find an exact match, do a fuzzy match (since we can't properly track editors)
const fuzzyProvider = Iterables.find(this._annotationProviders.values(), p => p.editor.document === e.textEditor.document);
const fuzzyProvider = Iterables.find(
this._annotationProviders.values(),
p => p.editor.document === e.textEditor.document
);
if (fuzzyProvider == null) return;
this.clearCore(fuzzyProvider.correlationKey, AnnotationClearReason.ColumnChanged);
@ -297,7 +326,11 @@ export class FileAnnotationController extends Disposable {
return this._annotationProviders.get(AnnotationProviderBase.getCorrelationKey(editor));
}
async show(editor: TextEditor | undefined, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
async show(
editor: TextEditor | undefined,
type: FileAnnotationType,
shaOrLine?: string | number
): Promise<boolean> {
if (this.getToggleMode(type) === AnnotationsToggleMode.Window) {
let first = this._annotationType === undefined;
const reset = !first && this._annotationType !== type;
@ -330,26 +363,44 @@ export class FileAnnotationController extends Disposable {
return true;
}
const provider = await window.withProgress({ location: ProgressLocation.Window }, async (progress: Progress<{ message: string }>) => {
await setCommandContext(CommandContext.AnnotationStatus, AnnotationStatus.Computing);
const computingAnnotations = this.showAnnotationsCore(currentProvider, editor, type, shaOrLine, progress);
const provider = await computingAnnotations;
const provider = await window.withProgress(
{ location: ProgressLocation.Window },
async (progress: Progress<{ message: string }>) => {
await setCommandContext(CommandContext.AnnotationStatus, AnnotationStatus.Computing);
const computingAnnotations = this.showAnnotationsCore(
currentProvider,
editor,
type,
shaOrLine,
progress
);
const provider = await computingAnnotations;
if (editor === this._editor) {
await setCommandContext(CommandContext.AnnotationStatus, provider && provider.status);
}
if (editor === this._editor) {
await setCommandContext(CommandContext.AnnotationStatus, provider && provider.status);
return computingAnnotations;
}
return computingAnnotations;
});
);
return provider !== undefined;
}
async toggle(editor: TextEditor | undefined, type: FileAnnotationType, shaOrLine?: string | number): Promise<boolean> {
async toggle(
editor: TextEditor | undefined,
type: FileAnnotationType,
shaOrLine?: string | number
): Promise<boolean> {
if (editor !== undefined) {
const trackedDocument = await Container.tracker.getOrAdd(editor.document);
if ((type === FileAnnotationType.RecentChanges && !trackedDocument.isTracked) || !trackedDocument.isBlameable) return false;
if (
(type === FileAnnotationType.RecentChanges && !trackedDocument.isTracked) ||
!trackedDocument.isBlameable
) {
return false;
}
}
const provider = this.getProvider(editor);
@ -417,7 +468,13 @@ export class FileAnnotationController extends Disposable {
this._keyboardScope = undefined;
}
private async showAnnotationsCore(currentProvider: AnnotationProviderBase | undefined, editor: TextEditor, type: FileAnnotationType, shaOrLine?: string | number, progress?: Progress<{ message: string }>): Promise<AnnotationProviderBase | undefined> {
private async showAnnotationsCore(
currentProvider: AnnotationProviderBase | undefined,
editor: TextEditor,
type: FileAnnotationType,
shaOrLine?: string | number,
progress?: Progress<{ message: string }>
): Promise<AnnotationProviderBase | undefined> {
if (progress !== undefined) {
let annotationsLabel = 'annotations';
switch (type) {
@ -434,7 +491,9 @@ export class FileAnnotationController extends Disposable {
break;
}
progress!.report({ message: `Computing ${annotationsLabel} for ${path.basename(editor.document.fileName)}` });
progress!.report({
message: `Computing ${annotationsLabel} for ${path.basename(editor.document.fileName)}`
});
}
// Allows pressing escape to exit the annotations
@ -445,15 +504,30 @@ export class FileAnnotationController extends Disposable {
let provider: AnnotationProviderBase | undefined = undefined;
switch (type) {
case FileAnnotationType.Blame:
provider = new GutterBlameAnnotationProvider(editor, trackedDocument, Decorations.blameAnnotation, Decorations.blameHighlight);
provider = new GutterBlameAnnotationProvider(
editor,
trackedDocument,
Decorations.blameAnnotation,
Decorations.blameHighlight
);
break;
case FileAnnotationType.Heatmap:
provider = new HeatmapBlameAnnotationProvider(editor, trackedDocument, Decorations.heatmapAnnotation, Decorations.heatmapHighlight);
provider = new HeatmapBlameAnnotationProvider(
editor,
trackedDocument,
Decorations.heatmapAnnotation,
Decorations.heatmapHighlight
);
break;
case FileAnnotationType.RecentChanges:
provider = new RecentChangesAnnotationProvider(editor, trackedDocument, Decorations.recentChangesAnnotation!, Decorations.recentChangesHighlight);
provider = new RecentChangesAnnotationProvider(
editor,
trackedDocument,
Decorations.recentChangesAnnotation!,
Decorations.recentChangesHighlight
);
break;
}
if (provider === undefined || !(await provider.validate())) return undefined;
@ -483,4 +557,4 @@ export class FileAnnotationController extends Disposable {
return undefined;
}
}
}

+ 19
- 9
src/annotations/gutterBlameAnnotationProvider.ts View File

@ -10,7 +10,6 @@ import { GitBlameCommit, ICommitFormatOptions } from '../gitService';
import { Logger } from '../logger';
export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
async onProvideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
this.annotationType = FileAnnotationType.Blame;
@ -22,11 +21,13 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
const cfg = Container.config.blame;
// Precalculate the formatting options so we don't need to do it on each iteration
const tokenOptions = Strings.getTokensFromTemplate(cfg.format)
.reduce((map, token) => {
const tokenOptions = Strings.getTokensFromTemplate(cfg.format).reduce(
(map, token) => {
map[token.key] = token.options as ICommitFormatOptions;
return map;
}, {} as { [token: string]: ICommitFormatOptions });
},
{} as { [token: string]: ICommitFormatOptions }
);
const options: ICommitFormatOptions = {
dateFormat: cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat,
@ -40,7 +41,9 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
this.decorations = [];
const decorationsMap: { [sha: string]: DecorationOptions | undefined } = Object.create(null);
const avatarDecorationsMap: { [email: string]: { decoration: TextEditorDecorationType, ranges: Range[] } } | undefined = avatars ? Object.create(null) : undefined;
const avatarDecorationsMap:
| { [email: string]: { decoration: TextEditorDecorationType; ranges: Range[] } }
| undefined = avatars ? Object.create(null) : undefined;
let commit: GitBlameCommit | undefined;
let compacted = false;
@ -64,7 +67,9 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
gutter.renderOptions = {
before: {
...gutter.renderOptions!.before,
contentText: GlyphChars.Space.repeat(Strings.width(gutter.renderOptions!.before!.contentText!))
contentText: GlyphChars.Space.repeat(
Strings.width(gutter.renderOptions!.before!.contentText!)
)
}
};
@ -138,14 +143,19 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
}
const duration = process.hrtime(start);
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute gutter blame annotations`);
Logger.log(`${duration[0] * 1000 + Math.floor(duration[1] / 1000000)} ms to compute gutter blame annotations`);
this.registerHoverProviders(Container.config.hovers.annotations);
this.selection(shaOrLine, blame);
return true;
}
addOrUpdateGravatarDecoration(commit: GitBlameCommit, range: Range, gravatarDefault: GravatarDefaultStyle, map: { [email: string]: { decoration: TextEditorDecorationType, ranges: Range[] } }) {
addOrUpdateGravatarDecoration(
commit: GitBlameCommit,
range: Range,
gravatarDefault: GravatarDefaultStyle,
map: { [email: string]: { decoration: TextEditorDecorationType; ranges: Range[] } }
) {
const avatarDecoration = map[commit.email!];
if (avatarDecoration !== undefined) {
avatarDecoration.ranges.push(range);
@ -161,4 +171,4 @@ export class GutterBlameAnnotationProvider extends BlameAnnotationProviderBase {
ranges: [range]
};
}
}
}

+ 1
- 2
src/annotations/heatmapBlameAnnotationProvider.ts View File

@ -8,7 +8,6 @@ import { GitBlameCommit } from '../gitService';
import { Logger } from '../logger';
export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase {
async onProvideAnnotation(shaOrLine?: string | number, type?: FileAnnotationType): Promise<boolean> {
this.annotationType = FileAnnotationType.Heatmap;
@ -57,7 +56,7 @@ export class HeatmapBlameAnnotationProvider extends BlameAnnotationProviderBase
}
const duration = process.hrtime(start);
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute heatmap annotations`);
Logger.log(`${duration[0] * 1000 + Math.floor(duration[1] / 1000000)} ms to compute heatmap annotations`);
this.registerHoverProviders(Container.config.hovers.annotations);
this.selection(shaOrLine, blame);

+ 1
- 1
src/annotations/hoverBlameAnnotationProvider.ts View File

@ -68,4 +68,4 @@
// this.selection(shaOrLine, blame);
// return true;
// }
// }
// }

+ 21
- 7
src/annotations/lineAnnotationController.ts View File

@ -1,5 +1,15 @@
'use strict';
import { ConfigurationChangeEvent, debug, DecorationRangeBehavior, DecorationRenderOptions, Disposable, Range, TextEditor, TextEditorDecorationType, window } from 'vscode';
import {
ConfigurationChangeEvent,
debug,
DecorationRangeBehavior,
DecorationRenderOptions,
Disposable,
Range,
TextEditor,
TextEditorDecorationType,
window
} from 'vscode';
import { Annotations } from './annotations';
import { configuration } from './../configuration';
import { isTextEditor } from './../constants';
@ -15,7 +25,6 @@ const annotationDecoration: TextEditorDecorationType = window.createTextEditorDe
} as DecorationRenderOptions);
export class LineAnnotationController extends Disposable {
private _disposable: Disposable;
private _debugSessionEndDisposable: Disposable | undefined;
private _editor: TextEditor | undefined;
@ -190,8 +199,15 @@ export class LineAnnotationController extends Disposable {
const state = Container.lineTracker.getState(l);
if (state === undefined || state.commit === undefined) continue;
const decoration = Annotations.trailing(state.commit, cfg.format, cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat, scrollable);
decoration.range = editor.document.validateRange(new Range(l, Number.MAX_SAFE_INTEGER, l, Number.MAX_SAFE_INTEGER));
const decoration = Annotations.trailing(
state.commit,
cfg.format,
cfg.dateFormat === null ? Container.config.defaultDateFormat : cfg.dateFormat,
scrollable
);
decoration.range = editor.document.validateRange(
new Range(l, Number.MAX_SAFE_INTEGER, l, Number.MAX_SAFE_INTEGER)
);
decorations.push(decoration);
}
@ -203,9 +219,7 @@ export class LineAnnotationController extends Disposable {
if (!Container.lineTracker.isSubscribed(this)) {
Container.lineTracker.start(
this,
Disposable.from(
Container.lineTracker.onDidChangeActiveLines(this.onActiveLinesChanged, this)
)
Disposable.from(Container.lineTracker.onDidChangeActiveLines(this.onActiveLinesChanged, this))
);
}

+ 56
- 12
src/annotations/lineHoverController.ts View File

@ -1,12 +1,24 @@
'use strict';
import { CancellationToken, ConfigurationChangeEvent, debug, Disposable, Hover, HoverProvider, languages, Position, Range, TextDocument, TextEditor, window } from 'vscode';
import {
CancellationToken,
ConfigurationChangeEvent,
debug,
Disposable,
Hover,
HoverProvider,
languages,
Position,
Range,
TextDocument,
TextEditor,
window
} from 'vscode';
import { Annotations } from './annotations';
import { configuration } from './../configuration';
import { Container } from './../container';
import { LinesChangeEvent } from './../trackers/gitLineTracker';
export class LineHoverController extends Disposable {
private _debugSessionEndDisposable: Disposable | undefined;
private _disposable: Disposable;
private _hoverProviderDisposable: Disposable | undefined;
@ -33,9 +45,13 @@ export class LineHoverController extends Disposable {
private onConfigurationChanged(e: ConfigurationChangeEvent) {
const initializing = configuration.initializing(e);
if (!initializing &&
if (
!initializing &&
!configuration.changed(e, configuration.name('hovers')('enabled').value) &&
!configuration.changed(e, configuration.name('hovers')('currentLine')('enabled').value)) return;
!configuration.changed(e, configuration.name('hovers')('currentLine')('enabled').value)
) {
return;
}
if (Container.config.hovers.enabled && Container.config.hovers.currentLine.enabled) {
Container.lineTracker.start(
@ -80,7 +96,11 @@ export class LineHoverController extends Disposable {
}
}
async provideDetailsHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
async provideDetailsHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
if (!Container.lineTracker.includes(position.line)) return undefined;
const lineState = Container.lineTracker.getState(position.line);
@ -95,13 +115,17 @@ export class LineHoverController extends Disposable {
// If we aren't showing the hover over the whole line, make sure the annotation is on
if (!wholeLine && Container.lineAnnotations.suspended) return undefined;
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER));
const range = document.validateRange(
new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER)
);
if (!wholeLine && range.start.character !== position.character) return undefined;
// Get the full commit message -- since blame only returns the summary
let logCommit = lineState !== undefined ? lineState.logCommit : undefined;
if (logCommit === undefined && !commit.isUncommitted) {
logCommit = await Container.git.getLogCommitForFile(commit.repoPath, commit.uri.fsPath, { ref: commit.sha });
logCommit = await Container.git.getLogCommitForFile(commit.repoPath, commit.uri.fsPath, {
ref: commit.sha
});
if (logCommit !== undefined) {
// Preserve the previous commit from the blame commit
logCommit.previousSha = commit.previousSha;
@ -116,11 +140,21 @@ export class LineHoverController extends Disposable {
const trackedDocument = await Container.tracker.get(document);
if (trackedDocument === undefined) return undefined;
const message = Annotations.getHoverMessage(logCommit || commit, Container.config.defaultDateFormat, await Container.git.getRemotes(commit.repoPath), fileAnnotations, position.line);
const message = Annotations.getHoverMessage(
logCommit || commit,
Container.config.defaultDateFormat,
await Container.git.getRemotes(commit.repoPath),
fileAnnotations,
position.line
);
return new Hover(message, range);
}
async provideChangesHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
async provideChangesHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
if (!Container.lineTracker.includes(position.line)) return undefined;
const lineState = Container.lineTracker.getState(position.line);
@ -137,7 +171,9 @@ export class LineHoverController extends Disposable {
// If we aren't showing the hover over the whole line, make sure the annotation is on
if (!wholeLine && Container.lineAnnotations.suspended) return undefined;
const range = document.validateRange(new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER));
const range = document.validateRange(
new Range(position.line, wholeLine ? 0 : Number.MAX_SAFE_INTEGER, position.line, Number.MAX_SAFE_INTEGER)
);
if (!wholeLine && range.start.character !== position.character) return undefined;
const trackedDocument = await Container.tracker.get(document);
@ -159,10 +195,18 @@ export class LineHoverController extends Disposable {
const subscriptions = [];
if (cfg.currentLine.changes) {
subscriptions.push(languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, { provideHover: this.provideChangesHover.bind(this) } as HoverProvider));
subscriptions.push(
languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, {
provideHover: this.provideChangesHover.bind(this)
} as HoverProvider)
);
}
if (cfg.currentLine.details) {
subscriptions.push(languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, { provideHover: this.provideDetailsHover.bind(this) } as HoverProvider));
subscriptions.push(
languages.registerHoverProvider({ pattern: editor.document.uri.fsPath }, {
provideHover: this.provideDetailsHover.bind(this)
} as HoverProvider)
);
}
this._hoverProviderDisposable = Disposable.from(...subscriptions);

+ 14
- 6
src/annotations/recentChangesAnnotationProvider.ts View File

@ -9,7 +9,6 @@ import { GitUri } from '../gitService';
import { Logger } from '../logger';
export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
private readonly _uri: GitUri;
constructor(
@ -48,14 +47,22 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
if (line.state === 'unchanged') continue;
const range = this.editor.document.validateRange(new Range(new Position(count, 0), new Position(count, Number.MAX_SAFE_INTEGER)));
const range = this.editor.document.validateRange(
new Range(new Position(count, 0), new Position(count, Number.MAX_SAFE_INTEGER))
);
let message: MarkdownString | undefined = undefined;
if (cfg.hovers.enabled && cfg.hovers.annotations.enabled) {
if (cfg.hovers.annotations.details) {
this.decorations.push({
hoverMessage: Annotations.getHoverMessage(commit, dateFormat, await Container.git.getRemotes(commit.repoPath), this.annotationType, this.editor.selection.active.line),
hoverMessage: Annotations.getHoverMessage(
commit,
dateFormat,
await Container.git.getRemotes(commit.repoPath),
this.annotationType,
this.editor.selection.active.line
),
range: range
} as DecorationOptions);
}
@ -76,13 +83,14 @@ export class RecentChangesAnnotationProvider extends AnnotationProviderBase {
this.editor.setDecorations(this.decoration, this.decorations);
const duration = process.hrtime(start);
Logger.log(`${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms to compute recent changes annotations`);
Logger.log(
`${duration[0] * 1000 + Math.floor(duration[1] / 1000000)} ms to compute recent changes annotations`
);
return true;
}
async selection(shaOrLine?: string | number): Promise<void> {
}
async selection(shaOrLine?: string | number): Promise<void> {}
async validate(): Promise<boolean> {
return true;

+ 11
- 7
src/codeLensController.ts View File

@ -3,12 +3,15 @@ import { ConfigurationChangeEvent, Disposable, languages } from 'vscode';
import { configuration } from './configuration';
import { CommandContext, setCommandContext } from './constants';
import { Container } from './container';
import { DocumentBlameStateChangeEvent, DocumentDirtyIdleTriggerEvent, GitDocumentState } from './trackers/gitDocumentTracker';
import {
DocumentBlameStateChangeEvent,
DocumentDirtyIdleTriggerEvent,
GitDocumentState
} from './trackers/gitDocumentTracker';
import { GitCodeLensProvider } from './gitCodeLensProvider';
import { Logger } from './logger';
export class CodeLensController extends Disposable {
private _canToggle: boolean = false;
private _disposable: Disposable | undefined;
private _provider: GitCodeLensProvider | undefined;
@ -17,9 +20,7 @@ export class CodeLensController extends Disposable {
constructor() {
super(() => this.dispose());
this._disposable = Disposable.from(
configuration.onDidChange(this.onConfigurationChanged, this)
);
this._disposable = Disposable.from(configuration.onDidChange(this.onConfigurationChanged, this));
this.onConfigurationChanged(configuration.initializingChangeEvent);
}
@ -32,9 +33,12 @@ export class CodeLensController extends Disposable {
const initializing = configuration.initializing(e);
const section = configuration.name('codeLens').value;
if (initializing || configuration.changed(e, section, null) ||
if (
initializing ||
configuration.changed(e, section, null) ||
configuration.changed(e, configuration.name('defaultDateStyle').value) ||
configuration.changed(e, configuration.name('defaultDateFormat').value)) {
configuration.changed(e, configuration.name('defaultDateFormat').value)
) {
if (!initializing) {
Logger.log('CodeLens config changed; resetting CodeLens provider');
}

+ 1
- 1
src/commands.ts View File

@ -104,4 +104,4 @@ export function configureCommands(): void {
Container.context.subscriptions.push(new Commands.ToggleLineBlameCommand());
Container.context.subscriptions.push(new Commands.ToggleReviewModeCommand());
Container.context.subscriptions.push(new Commands.ToggleZenModeCommand());
}
}

+ 1
- 2
src/commands/clearFileAnnotations.ts View File

@ -6,7 +6,6 @@ import { Container } from '../container';
import { Logger } from '../logger';
export class ClearFileAnnotationsCommand extends EditorCommand {
constructor() {
super([Commands.ClearFileAnnotations, Commands.ComputingFileAnnotations]);
}
@ -30,4 +29,4 @@ export class ClearFileAnnotationsCommand extends EditorCommand {
return window.showErrorMessage(`Unable to clear file annotations. See output channel for more details`);
}
}
}
}

+ 11
- 5
src/commands/closeUnchangedFiles.ts View File

@ -12,7 +12,6 @@ export interface CloseUnchangedFilesCommandArgs {
}
export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
constructor() {
super(Commands.CloseUnchangedFiles);
}
@ -24,7 +23,11 @@ export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
if (args.uris === undefined) {
args = { ...args };
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Close unchanged files in which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Close unchanged files in which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const status = await Container.git.getStatusForRepo(repoPath);
@ -46,8 +49,11 @@ export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
break;
}
if (editor.document !== undefined &&
(editor.document.isDirty || args.uris.some(uri => UriComparer.equals(uri, editor!.document && editor!.document.uri)))) {
if (
editor.document !== undefined &&
(editor.document.isDirty ||
args.uris.some(uri => UriComparer.equals(uri, editor!.document && editor!.document.uri)))
) {
const lastPrevious = previous;
previous = editor;
editor = await editorTracker.awaitNext(500);
@ -83,4 +89,4 @@ export class CloseUnchangedFilesCommand extends ActiveEditorCommand {
return window.showErrorMessage(`Unable to close unchanged files. See output channel for more details`);
}
}
}
}

+ 88
- 25
src/commands/common.ts View File

@ -1,5 +1,17 @@
'use strict';
import { commands, Disposable, SourceControlResourceGroup, SourceControlResourceState, TextDocumentShowOptions, TextEditor, TextEditorEdit, Uri, ViewColumn, window, workspace } from 'vscode';
import {
commands,
Disposable,
SourceControlResourceGroup,
SourceControlResourceState,
TextDocumentShowOptions,
TextEditor,
TextEditorEdit,
Uri,
ViewColumn,
window,
workspace
} from 'vscode';
import { BuiltInCommands, DocumentSchemes, ImageExtensions } from '../constants';
import { Container } from '../container';
import { ExplorerNode, ExplorerRefNode } from '../views/explorerNodes';
@ -78,7 +90,12 @@ export function getCommandUri(uri?: Uri, editor?: TextEditor): Uri | undefined {
return document.uri;
}
export async function getRepoPathOrActiveOrPrompt(uri: Uri | undefined, editor: TextEditor | undefined, placeholder: string, goBackCommand?: CommandQuickPickItem) {
export async function getRepoPathOrActiveOrPrompt(
uri: Uri | undefined,
editor: TextEditor | undefined,
placeholder: string,
goBackCommand?: CommandQuickPickItem
) {
let repoPath = await Container.git.getRepoPathOrActive(uri, editor);
if (!repoPath) {
const pick = await RepositoriesQuickPick.show(placeholder, goBackCommand);
@ -133,28 +150,52 @@ export interface CommandViewContext extends CommandBaseContext {
node: ExplorerNode;
}
export function isCommandViewContextWithBranch(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { branch: GitBranch }) } {
return context.type === 'view' && (context.node as (ExplorerNode & { branch?: GitBranch })).branch instanceof GitBranch;
export function isCommandViewContextWithBranch(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { branch: GitBranch } } {
return (
context.type === 'view' && (context.node as ExplorerNode & { branch?: GitBranch }).branch instanceof GitBranch
);
}
export function isCommandViewContextWithCommit<T extends GitCommit>(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { commit: T }) } {
return context.type === 'view' && (context.node as (ExplorerNode & { commit?: GitCommit })).commit instanceof GitCommit;
export function isCommandViewContextWithCommit<T extends GitCommit>(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { commit: T } } {
return (
context.type === 'view' && (context.node as ExplorerNode & { commit?: GitCommit }).commit instanceof GitCommit
);
}
export function isCommandViewContextWithRef(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { ref: string }) } {
return context.type === 'view' && (context.node instanceof ExplorerRefNode);
export function isCommandViewContextWithRef(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { ref: string } } {
return context.type === 'view' && context.node instanceof ExplorerRefNode;
}
export function isCommandViewContextWithRemote(context: CommandContext): context is CommandViewContext & { node: (ExplorerNode & { remote: GitRemote }) } {
return context.type === 'view' && (context.node as (ExplorerNode & { remote?: GitRemote })).remote instanceof GitRemote;
export function isCommandViewContextWithRemote(
context: CommandContext
): context is CommandViewContext & { node: ExplorerNode & { remote: GitRemote } } {
return (
context.type === 'view' && (context.node as ExplorerNode & { remote?: GitRemote }).remote instanceof GitRemote
);
}
export type CommandContext = CommandScmGroupsContext | CommandScmStatesContext | CommandUnknownContext | CommandUriContext | CommandViewContext;
export type CommandContext =
| CommandScmGroupsContext
| CommandScmStatesContext
| CommandUnknownContext
| CommandUriContext
| CommandViewContext;
function isScmResourceGroup(group: any): group is SourceControlResourceGroup {
if (group == null) return false;
return (group as SourceControlResourceGroup).id !== undefined && (group.handle !== undefined || (group as SourceControlResourceGroup).label !== undefined || (group as SourceControlResourceGroup).resourceStates !== undefined);
return (
(group as SourceControlResourceGroup).id !== undefined &&
(group.handle !== undefined ||
(group as SourceControlResourceGroup).label !== undefined ||
(group as SourceControlResourceGroup).resourceStates !== undefined)
);
}
function isScmResourceState(state: any): state is SourceControlResourceState {
@ -166,11 +207,13 @@ function isScmResourceState(state: any): state is SourceControlResourceState {
function isTextEditor(editor: any): editor is TextEditor {
if (editor == null) return false;
return editor.id !== undefined && ((editor as TextEditor).edit !== undefined || (editor as TextEditor).document !== undefined);
return (
editor.id !== undefined &&
((editor as TextEditor).edit !== undefined || (editor as TextEditor).document !== undefined)
);
}
export abstract class Command extends Disposable {
static getMarkdownCommandArgsCore<T>(command: Commands, args: T): string {
return `command:${command}?${encodeURIComponent(JSON.stringify(args))}`;
}
@ -183,12 +226,18 @@ export abstract class Command extends Disposable {
super(() => this.dispose());
if (typeof command === 'string') {
this._disposable = commands.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
this._disposable = commands.registerCommand(
command,
(...args: any[]) => this._execute(command, ...args),
this
);
return;
}
const subscriptions = command.map(cmd => commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
const subscriptions = command.map(cmd =>
commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this)
);
this._disposable = Disposable.from(...subscriptions);
}
@ -209,7 +258,11 @@ export abstract class Command extends Disposable {
return this.preExecute(context, ...rest);
}
private static parseContext(command: string, options: CommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
private static parseContext(
command: string,
options: CommandContextParsingOptions,
...args: any[]
): [CommandContext, any[]] {
let editor: TextEditor | undefined = undefined;
let firstArg = args[0];
@ -239,7 +292,10 @@ export abstract class Command extends Disposable {
states.push(arg);
}
return [{ command: command, type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri }, args.slice(count)];
return [
{ command: command, type: 'scm-states', scmResourceStates: states, uri: states[0].resourceUri },
args.slice(count)
];
}
if (isScmResourceGroup(firstArg)) {
@ -260,7 +316,6 @@ export abstract class Command extends Disposable {
}
export abstract class ActiveEditorCommand extends Command {
protected readonly contextParsingOptions: CommandContextParsingOptions = { editor: true, uri: true };
constructor(command: Commands | Commands[]) {
@ -278,13 +333,12 @@ export abstract class ActiveEditorCommand extends Command {
abstract execute(editor?: TextEditor, ...args: any[]): any;
}
let lastCommand: { command: string, args: any[] } | undefined = undefined;
let lastCommand: { command: string; args: any[] } | undefined = undefined;
export function getLastCommand() {
return lastCommand;
}
export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
constructor(command: Commands | Commands[]) {
super(command);
}
@ -301,7 +355,6 @@ export abstract class ActiveEditorCachedCommand extends ActiveEditorCommand {
}
export abstract class EditorCommand extends Disposable {
private _disposable: Disposable;
constructor(command: Commands | Commands[]) {
@ -313,7 +366,14 @@ export abstract class EditorCommand extends Disposable {
const subscriptions = [];
for (const cmd of command) {
subscriptions.push(commands.registerTextEditorCommand(cmd, (editor: TextEditor, edit: TextEditorEdit, ...args: any[]) => this.executeCore(cmd, editor, edit, ...args), this));
subscriptions.push(
commands.registerTextEditorCommand(
cmd,
(editor: TextEditor, edit: TextEditorEdit, ...args: any[]) =>
this.executeCore(cmd, editor, edit, ...args),
this
)
);
}
this._disposable = Disposable.from(...subscriptions);
}
@ -330,7 +390,10 @@ export abstract class EditorCommand extends Disposable {
abstract execute(editor: TextEditor, edit: TextEditorEdit, ...args: any[]): any;
}
export async function openEditor(uri: Uri, options: TextDocumentShowOptions & { rethrow?: boolean } = {}): Promise<TextEditor | undefined> {
export async function openEditor(
uri: Uri,
options: TextDocumentShowOptions & { rethrow?: boolean } = {}
): Promise<TextEditor | undefined> {
const { rethrow, ...opts } = options;
try {
if (uri instanceof GitUri) {
@ -373,4 +436,4 @@ export async function openEditor(uri: Uri, options: TextDocumentShowOptions & {
Logger.error(ex, 'openEditor');
return undefined;
}
}
}

+ 13
- 7
src/commands/copyMessageToClipboard.ts View File

@ -12,7 +12,6 @@ export interface CopyMessageToClipboardCommandArgs {
}
export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
constructor() {
super(Commands.CopyMessageToClipboard);
}
@ -53,9 +52,14 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
if (blameline < 0) return undefined;
try {
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(
gitUri,
blameline,
editor.document.getText()
)
: await Container.git.getBlameForLine(gitUri, blameline);
if (!blame) return undefined;
if (blame.commit.isUncommitted) return undefined;
@ -78,12 +82,14 @@ export class CopyMessageToClipboardCommand extends ActiveEditorCommand {
args.message = commit.message;
}
void await clipboard.write(args.message);
void (await clipboard.write(args.message));
return undefined;
}
catch (ex) {
if (ex.message.includes('Couldn\'t find the required `xsel` binary')) {
window.showErrorMessage(`Unable to copy message, xsel is not installed. You can install it via \`sudo apt install xsel\``);
if (ex.message.includes("Couldn't find the required `xsel` binary")) {
window.showErrorMessage(
`Unable to copy message, xsel is not installed. You can install it via \`sudo apt install xsel\``
);
return;
}

+ 9
- 7
src/commands/copyShaToClipboard.ts View File

@ -11,7 +11,6 @@ export interface CopyShaToClipboardCommandArgs {
}
export class CopyShaToClipboardCommand extends ActiveEditorCommand {
constructor() {
super(Commands.CopyShaToClipboard);
}
@ -50,9 +49,10 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
try {
const gitUri = await GitUri.fromUri(uri);
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return undefined;
args.sha = blame.commit.sha;
@ -63,12 +63,14 @@ export class CopyShaToClipboardCommand extends ActiveEditorCommand {
}
}
void await clipboard.write(args.sha);
void (await clipboard.write(args.sha));
return undefined;
}
catch (ex) {
if (ex.message.includes('Couldn\'t find the required `xsel` binary')) {
window.showErrorMessage(`Unable to copy commit id, xsel is not installed. You can install it via \`sudo apt install xsel\``);
if (ex.message.includes("Couldn't find the required `xsel` binary")) {
window.showErrorMessage(
`Unable to copy commit id, xsel is not installed. You can install it via \`sudo apt install xsel\``
);
return;
}

+ 10
- 5
src/commands/diffBranchWithBranch.ts View File

@ -12,7 +12,6 @@ export interface DiffBranchWithBranchCommandArgs {
}
export class DiffBranchWithBranchCommand extends ActiveEditorCommand {
constructor() {
super([Commands.DiffHeadWithBranch, Commands.DiffWorkingWithBranch]);
}
@ -39,7 +38,11 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand {
let progressCancellation: CancellationTokenSource | undefined;
try {
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Compare with branch or tag in which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Compare with branch or tag in which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
if (!args.ref1) {
@ -56,7 +59,7 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand {
break;
}
progressCancellation = BranchesAndTagsQuickPick.showProgress(placeHolder);
progressCancellation = BranchesAndTagsQuickPick.showProgress(placeHolder);
const [branches, tags] = await Promise.all([
Container.git.getBranches(repoPath),
@ -65,7 +68,9 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand {
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, { progressCancellation: progressCancellation });
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, {
progressCancellation: progressCancellation
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
@ -86,4 +91,4 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand {
progressCancellation && progressCancellation.cancel();
}
}
}
}

+ 23
- 7
src/commands/diffDirectory.ts View File

@ -14,9 +14,13 @@ export interface DiffDirectoryCommandArgs {
}
export class DiffDirectoryCommand extends ActiveEditorCommand {
constructor() {
super([Commands.DiffDirectory, Commands.ExternalDiffAll, Commands.ExplorersOpenDirectoryDiff, Commands.ExplorersOpenDirectoryDiffWithWorking]);
super([
Commands.DiffDirectory,
Commands.ExternalDiffAll,
Commands.ExplorersOpenDirectoryDiff,
Commands.ExplorersOpenDirectoryDiffWithWorking
]);
}
protected async preExecute(context: CommandContext, args: DiffDirectoryCommandArgs = {}): Promise<any> {
@ -50,7 +54,11 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
let progressCancellation: CancellationTokenSource | undefined;
try {
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Compare directory in which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Compare directory in which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
if (!args.ref1) {
@ -67,7 +75,9 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, { progressCancellation: progressCancellation });
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, {
progressCancellation: progressCancellation
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
@ -82,10 +92,16 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
catch (ex) {
const msg = ex && ex.toString();
if (msg === 'No diff tool found') {
const result = await window.showWarningMessage(`Unable to open directory compare because there is no Git diff tool configured`, 'View Git Docs');
const result = await window.showWarningMessage(
`Unable to open directory compare because there is no Git diff tool configured`,
'View Git Docs'
);
if (!result) return undefined;
return commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool'));
return commands.executeCommand(
BuiltInCommands.Open,
Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool')
);
}
Logger.error(ex, 'DiffDirectoryCommand');
@ -95,4 +111,4 @@ export class DiffDirectoryCommand extends ActiveEditorCommand {
progressCancellation && progressCancellation.cancel();
}
}
}
}

+ 8
- 6
src/commands/diffLineWithPrevious.ts View File

@ -15,7 +15,6 @@ export interface DiffLineWithPreviousCommandArgs {
}
export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffLineWithPrevious);
}
@ -36,10 +35,13 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
if (blameline < 0) return undefined;
try {
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
args.commit = blame.commit;
@ -74,4 +76,4 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand {
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
}
}

+ 11
- 8
src/commands/diffLineWithWorking.ts View File

@ -15,7 +15,6 @@ export interface DiffLineWithWorkingCommandArgs {
}
export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffLineWithWorking);
}
@ -36,10 +35,13 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
if (blameline < 0) return undefined;
try {
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
args.commit = blame.commit;
@ -47,9 +49,10 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand {
if (args.commit.isUncommitted) {
const status = await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
args.commit = args.commit.with({
sha: status !== undefined && status.indexStatus !== undefined
? GitService.stagedUncommittedSha
: args.commit.previousSha!,
sha:
status !== undefined && status.indexStatus !== undefined
? GitService.stagedUncommittedSha
: args.commit.previousSha!,
fileName: args.commit.previousFileName!,
originalFileName: null,
previousSha: null,

+ 27
- 14
src/commands/diffWith.ts View File

@ -23,7 +23,6 @@ export interface DiffWithCommandArgs {
}
export class DiffWithCommand extends ActiveEditorCommand {
static getMarkdownCommandArgs(args: DiffWithCommandArgs): string;
static getMarkdownCommandArgs(commit1: GitCommit, commit2: GitCommit): string;
static getMarkdownCommandArgs(argsOrCommit1: DiffWithCommandArgs | GitCommit, commit2?: GitCommit): string {
@ -85,7 +84,12 @@ export class DiffWithCommand extends ActiveEditorCommand {
}
async execute(editor?: TextEditor, uri?: Uri, args: DiffWithCommandArgs = {}): Promise<any> {
args = { ...args, lhs: { ...args.lhs }, rhs: { ...args.rhs }, showOptions: { ...args.showOptions} } as DiffWithCommandArgs;
args = {
...args,
lhs: { ...args.lhs },
rhs: { ...args.rhs },
showOptions: { ...args.showOptions }
} as DiffWithCommandArgs;
if (args.repoPath === undefined || args.lhs === undefined || args.rhs === undefined) return undefined;
try {
@ -105,9 +109,7 @@ export class DiffWithCommand extends ActiveEditorCommand {
let rhsPrefix = '';
if (rhs === undefined) {
rhsPrefix = GitService.isUncommitted(args.rhs.sha)
? ' (deleted)'
: 'deleted in ';
rhsPrefix = GitService.isUncommitted(args.rhs.sha) ? ' (deleted)' : 'deleted in ';
}
else if (lhs === undefined || args.lhs.sha === GitService.deletedSha) {
rhsPrefix = 'added in ';
@ -124,18 +126,27 @@ export class DiffWithCommand extends ActiveEditorCommand {
}
}
if (args.lhs.title === undefined && args.lhs.sha !== GitService.deletedSha && (lhs !== undefined || lhsPrefix !== '')) {
if (
args.lhs.title === undefined &&
args.lhs.sha !== GitService.deletedSha &&
(lhs !== undefined || lhsPrefix !== '')
) {
const suffix = GitService.shortenSha(args.lhs.sha) || '';
args.lhs.title = `${path.basename(args.lhs.uri.fsPath)}${suffix !== '' ? ` (${lhsPrefix}${suffix})` : ''}`;
args.lhs.title = `${path.basename(args.lhs.uri.fsPath)}${
suffix !== '' ? ` (${lhsPrefix}${suffix})` : ''
}`;
}
if (args.rhs.title === undefined && args.rhs.sha !== GitService.deletedSha) {
const suffix = GitService.shortenSha(args.rhs.sha, { uncommitted: 'working tree' }) || '';
args.rhs.title = `${path.basename(args.rhs.uri.fsPath)}${suffix !== '' ? ` (${rhsPrefix}${suffix})` : rhsPrefix}`;
args.rhs.title = `${path.basename(args.rhs.uri.fsPath)}${
suffix !== '' ? ` (${rhsPrefix}${suffix})` : rhsPrefix
}`;
}
const title = (args.lhs.title !== undefined && args.rhs.title !== undefined)
? `${args.lhs.title} ${GlyphChars.ArrowLeftRightLong} ${args.rhs.title}`
: args.lhs.title || args.rhs.title;
const title =
args.lhs.title !== undefined && args.rhs.title !== undefined
? `${args.lhs.title} ${GlyphChars.ArrowLeftRightLong} ${args.rhs.title}`
: args.lhs.title || args.rhs.title;
if (args.showOptions === undefined) {
args.showOptions = {};
@ -149,7 +160,8 @@ export class DiffWithCommand extends ActiveEditorCommand {
args.showOptions.selection = new Range(args.line, 0, args.line, 0);
}
return await commands.executeCommand(BuiltInCommands.Diff,
return await commands.executeCommand(
BuiltInCommands.Diff,
lhs === undefined
? GitUri.toRevisionUri(GitService.deletedSha, args.lhs.uri.fsPath, args.repoPath)
: Uri.file(lhs),
@ -157,11 +169,12 @@ export class DiffWithCommand extends ActiveEditorCommand {
? GitUri.toRevisionUri(GitService.deletedSha, args.rhs.uri.fsPath, args.repoPath)
: Uri.file(rhs),
title,
args.showOptions);
args.showOptions
);
}
catch (ex) {
Logger.error(ex, 'DiffWithCommand', 'getVersionedFile');
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}
}
}

+ 6
- 4
src/commands/diffWithBranch.ts View File

@ -18,7 +18,6 @@ export interface DiffWithBranchCommandArgs {
}
export class DiffWithBranchCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffWithBranch);
}
@ -46,7 +45,10 @@ export class DiffWithBranchCommand extends ActiveEditorCommand {
if (progressCancellation.token.isCancellationRequested) return undefined;
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, { progressCancellation: progressCancellation, goBackCommand: args.goBackCommand });
const pick = await BranchesAndTagsQuickPick.show(branches, tags, placeHolder, {
progressCancellation: progressCancellation,
goBackCommand: args.goBackCommand
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
@ -72,7 +74,7 @@ export class DiffWithBranchCommand extends ActiveEditorCommand {
repoPath: gitUri.repoPath,
lhs: {
sha: pick.remote ? `remotes/${ref}` : ref,
uri: renamedUri || gitUri as Uri,
uri: renamedUri || (gitUri as Uri),
title: renamedTitle || `${path.basename(gitUri.fsPath)} (${ref})`
},
rhs: {
@ -88,4 +90,4 @@ export class DiffWithBranchCommand extends ActiveEditorCommand {
progressCancellation.cancel();
}
}
}
}

+ 10
- 5
src/commands/diffWithNext.ts View File

@ -17,7 +17,6 @@ export interface DiffWithNextCommandArgs {
}
export class DiffWithNextCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffWithNext);
}
@ -55,8 +54,14 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, { maxCount: sha !== undefined ? undefined : 2, range: args.range!, renames: true });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: sha !== undefined ? undefined : 2,
range: args.range!,
renames: true
});
if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
@ -74,7 +79,7 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
if (args.commit.nextSha === undefined) {
// Check if the file is staged
status = status || await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath);
status = status || (await Container.git.getStatusForFile(gitUri.repoPath!, gitUri.fsPath));
if (status !== undefined && status.indexStatus === 'M') {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
@ -111,4 +116,4 @@ export class DiffWithNextCommand extends ActiveEditorCommand {
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
}
}

+ 16
- 6
src/commands/diffWithPrevious.ts View File

@ -18,7 +18,6 @@ export interface DiffWithPreviousCommandArgs {
}
export class DiffWithPreviousCommand extends ActiveEditorCommand {
constructor() {
super([Commands.DiffWithPrevious, Commands.DiffWithPreviousInDiff]);
}
@ -59,8 +58,14 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
sha = sha + '^';
}
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, { maxCount: 2, ref: sha, renames: true });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: 2,
ref: sha,
renames: true
});
if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
args.commit = (sha && log.commits.get(sha)) || Iterables.first(log.commits.values());
@ -73,7 +78,9 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
const diffArgs: DiffWithCommandArgs = {
repoPath: args.commit.repoPath,
lhs: {
sha: args.inDiffEditor ? args.commit.previousSha || GitService.deletedSha : args.commit.sha,
sha: args.inDiffEditor
? args.commit.previousSha || GitService.deletedSha
: args.commit.sha,
uri: args.inDiffEditor ? args.commit.previousUri : args.commit.uri
},
rhs: {
@ -106,7 +113,10 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
}
if (!args.inDiffEditor) {
return commands.executeCommand(Commands.DiffWithWorking, uri, { commit: args.commit, showOptions: args.showOptions } as DiffWithWorkingCommandArgs);
return commands.executeCommand(Commands.DiffWithWorking, uri, {
commit: args.commit,
showOptions: args.showOptions
} as DiffWithWorkingCommandArgs);
}
}
}
@ -132,4 +142,4 @@ export class DiffWithPreviousCommand extends ActiveEditorCommand {
};
return commands.executeCommand(Commands.DiffWith, diffArgs);
}
}
}

+ 51
- 24
src/commands/diffWithRevision.ts View File

@ -19,7 +19,6 @@ export interface DiffWithRevisionCommandArgs {
}
export class DiffWithRevisionCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffWithRevision);
}
@ -35,47 +34,75 @@ export class DiffWithRevisionCommand extends ActiveEditorCommand {
const gitUri = await GitUri.fromUri(uri);
const placeHolder = `Compare ${gitUri.getFormattedPath()}${gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''} with ${GlyphChars.Ellipsis}`;
const placeHolder = `Compare ${gitUri.getFormattedPath()}${
gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''
} with ${GlyphChars.Ellipsis}`;
const progressCancellation = FileHistoryQuickPick.showProgress(placeHolder);
try {
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, { maxCount: args.maxCount, ref: gitUri.sha });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open history compare');
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: args.maxCount,
ref: gitUri.sha
});
if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open history compare');
}
if (progressCancellation.token.isCancellationRequested) return undefined;
let previousPageCommand: CommandQuickPickItem | undefined = undefined;
if (log.truncated) {
const npc = new CommandQuickPickItem({
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} newer commits`
}, Commands.DiffWithRevision, [uri, { ...args } as DiffWithRevisionCommandArgs]);
const npc = new CommandQuickPickItem(
{
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} newer commits`
},
Commands.DiffWithRevision,
[uri, { ...args } as DiffWithRevisionCommandArgs]
);
const last = Iterables.last(log.commits.values());
if (last != null) {
previousPageCommand = new CommandQuickPickItem({
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} older commits`
}, Commands.DiffWithRevision, [new GitUri(uri, last), { ...args, nextPageCommand: npc } as DiffWithRevisionCommandArgs]);
previousPageCommand = new CommandQuickPickItem(
{
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} older commits`
},
Commands.DiffWithRevision,
[new GitUri(uri, last), { ...args, nextPageCommand: npc } as DiffWithRevisionCommandArgs]
);
}
}
const pick = await FileHistoryQuickPick.show(log, gitUri, placeHolder, {
pickerOnly: true,
progressCancellation: progressCancellation,
currentCommand: new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${gitUri.getFormattedPath()}${gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''}`
}, Commands.DiffWithRevision, [uri, { ...args }]),
currentCommand: new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${
GlyphChars.Space
}$(file-text) ${gitUri.getFormattedPath()}${
gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''
}`
},
Commands.DiffWithRevision,
[uri, { ...args }]
),
nextPageCommand: args.nextPageCommand,
previousPageCommand: previousPageCommand,
showAllCommand: log !== undefined && log.truncated
? new CommandQuickPickItem({
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
}, Commands.DiffWithRevision, [uri, { ...args, maxCount: 0 }])
: undefined
showAllCommand:
log !== undefined && log.truncated
? new CommandQuickPickItem(
{
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
},
Commands.DiffWithRevision,
[uri, { ...args, maxCount: 0 }]
)
: undefined
});
if (pick === undefined) return undefined;
@ -117,5 +144,5 @@ export class DiffWithRevisionCommand extends ActiveEditorCommand {
finally {
progressCancellation.cancel();
}
}
}
}
}

+ 12
- 4
src/commands/diffWithWorking.ts View File

@ -15,7 +15,6 @@ export interface DiffWithWorkingCommandArgs {
}
export class DiffWithWorkingCommand extends ActiveEditorCommand {
constructor() {
super(Commands.DiffWithWorking);
}
@ -60,11 +59,20 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand {
}
try {
args.commit = await Container.git.getLogCommitForFile(gitUri.repoPath, gitUri.fsPath, { ref: gitUri.sha, firstIfNotFound: true });
if (args.commit === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
args.commit = await Container.git.getLogCommitForFile(gitUri.repoPath, gitUri.fsPath, {
ref: gitUri.sha,
firstIfNotFound: true
});
if (args.commit === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open compare');
}
}
catch (ex) {
Logger.error(ex, 'DiffWithWorkingCommand', `getLogCommit(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`);
Logger.error(
ex,
'DiffWithWorkingCommand',
`getLogCommit(${gitUri.repoPath}, ${gitUri.fsPath}, ${gitUri.sha})`
);
return window.showErrorMessage(`Unable to open compare. See output channel for more details`);
}
}

+ 28
- 11
src/commands/externalDiff.ts View File

@ -40,11 +40,10 @@ interface Resource extends SourceControlResourceState {
}
class ExternalDiffFile {
constructor(
public readonly uri: Uri,
public readonly staged: boolean
) { }
) {}
}
export interface ExternalDiffCommandArgs {
@ -52,7 +51,6 @@ export interface ExternalDiffCommandArgs {
}
export class ExternalDiffCommand extends Command {
constructor() {
super(Commands.ExternalDiff);
}
@ -60,15 +58,24 @@ export class ExternalDiffCommand extends Command {
protected async preExecute(context: CommandContext, args: ExternalDiffCommandArgs = {}): Promise<any> {
if (context.type === 'scm-states') {
args = { ...args };
args.files = context.scmResourceStates
.map(r => new ExternalDiffFile(r.resourceUri, (r as Resource).resourceGroupType === ResourceGroupType.Index));
args.files = context.scmResourceStates.map(
r => new ExternalDiffFile(r.resourceUri, (r as Resource).resourceGroupType === ResourceGroupType.Index)
);
return this.execute(args);
}
else if (context.type === 'scm-groups') {
args = { ...args };
args.files = Arrays.filterMap(context.scmResourceGroups[0].resourceStates,
r => this.isModified(r) ? new ExternalDiffFile(r.resourceUri, (r as Resource).resourceGroupType === ResourceGroupType.Index) : undefined);
args.files = Arrays.filterMap(
context.scmResourceGroups[0].resourceStates,
r =>
this.isModified(r)
? new ExternalDiffFile(
r.resourceUri,
(r as Resource).resourceGroupType === ResourceGroupType.Index
)
: undefined
);
return this.execute(args);
}
@ -83,15 +90,25 @@ export class ExternalDiffCommand extends Command {
async execute(args: ExternalDiffCommandArgs = {}) {
try {
const repoPath = await getRepoPathOrActiveOrPrompt(undefined, undefined, `Open changes from which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
undefined,
undefined,
`Open changes from which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const tool = await Container.git.getDiffTool(repoPath);
if (tool === undefined) {
const result = await window.showWarningMessage(`Unable to open changes in diff tool because there is no Git diff tool configured`, 'View Git Docs');
const result = await window.showWarningMessage(
`Unable to open changes in diff tool because there is no Git diff tool configured`,
'View Git Docs'
);
if (!result) return undefined;
return commands.executeCommand(BuiltInCommands.Open, Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool'));
return commands.executeCommand(
BuiltInCommands.Open,
Uri.parse('https://git-scm.com/docs/git-config#git-config-difftool')
);
}
if (args.files === undefined) {
@ -122,4 +139,4 @@ export class ExternalDiffCommand extends Command {
return window.showErrorMessage(`Unable to open changes in diff tool. See output channel for more details`);
}
}
}
}

+ 22
- 7
src/commands/openBranchInRemote.ts View File

@ -1,6 +1,13 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, getRepoPathOrActiveOrPrompt, isCommandViewContextWithBranch } from './common';
import {
ActiveEditorCommand,
CommandContext,
Commands,
getCommandUri,
getRepoPathOrActiveOrPrompt,
isCommandViewContextWithBranch
} from './common';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitUri } from '../gitService';
@ -14,7 +21,6 @@ export interface OpenBranchInRemoteCommandArgs {
}
export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenBranchInRemote);
}
@ -32,9 +38,13 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
async execute(editor?: TextEditor, uri?: Uri, args: OpenBranchInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri);
const gitUri = uri && (await GitUri.fromUri(uri));
const repoPath = await getRepoPathOrActiveOrPrompt(gitUri, editor, `Open branch in remote for which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
gitUri,
editor,
`Open branch in remote for which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
try {
@ -43,7 +53,10 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
const branches = (await Container.git.getBranches(repoPath)).filter(b => b.tracking !== undefined);
if (branches.length > 1) {
const pick = await BranchesQuickPick.show(branches, `Open which branch in remote${GlyphChars.Ellipsis}`);
const pick = await BranchesQuickPick.show(
branches,
`Open which branch in remote${GlyphChars.Ellipsis}`
);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return undefined;
@ -68,7 +81,9 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'OpenBranchInRemoteCommandArgs');
return window.showErrorMessage(`Unable to open branch in remote provider. See output channel for more details`);
return window.showErrorMessage(
`Unable to open branch in remote provider. See output channel for more details`
);
}
}
}
}

+ 18
- 6
src/commands/openBranchesInRemote.ts View File

@ -1,6 +1,13 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, getRepoPathOrActiveOrPrompt, isCommandViewContextWithRemote } from './common';
import {
ActiveEditorCommand,
CommandContext,
Commands,
getCommandUri,
getRepoPathOrActiveOrPrompt,
isCommandViewContextWithRemote
} from './common';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitUri } from '../gitService';
@ -12,7 +19,6 @@ export interface OpenBranchesInRemoteCommandArgs {
}
export class OpenBranchesInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenBranchesInRemote);
}
@ -29,9 +35,13 @@ export class OpenBranchesInRemoteCommand extends ActiveEditorCommand {
async execute(editor?: TextEditor, uri?: Uri, args: OpenBranchesInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri);
const gitUri = uri && (await GitUri.fromUri(uri));
const repoPath = await getRepoPathOrActiveOrPrompt(gitUri, editor, `Open branches in remote for which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
gitUri,
editor,
`Open branches in remote for which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
try {
@ -47,7 +57,9 @@ export class OpenBranchesInRemoteCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'OpenBranchesInRemoteCommand');
return window.showErrorMessage(`Unable to open branches in remote provider. See output channel for more details`);
return window.showErrorMessage(
`Unable to open branches in remote provider. See output channel for more details`
);
}
}
}
}

+ 7
- 5
src/commands/openChangedFiles.ts View File

@ -11,7 +11,6 @@ export interface OpenChangedFilesCommandArgs {
}
export class OpenChangedFilesCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenChangedFiles);
}
@ -23,14 +22,17 @@ export class OpenChangedFilesCommand extends ActiveEditorCommand {
if (args.uris === undefined) {
args = { ...args };
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Open changed files in which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Open changed files in which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const status = await Container.git.getStatusForRepo(repoPath);
if (status === undefined) return window.showWarningMessage(`Unable to open changed files`);
args.uris = Arrays.filterMap(status.files,
f => f.status !== 'D' ? f.uri : undefined);
args.uris = Arrays.filterMap(status.files, f => (f.status !== 'D' ? f.uri : undefined));
}
for (const uri of args.uris) {
@ -44,4 +46,4 @@ export class OpenChangedFilesCommand extends ActiveEditorCommand {
return window.showErrorMessage(`Unable to open changed files. See output channel for more details`);
}
}
}
}

+ 14
- 10
src/commands/openCommitInRemote.ts View File

@ -12,13 +12,10 @@ export interface OpenCommitInRemoteCommandArgs {
}
export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: OpenCommitInRemoteCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: OpenCommitInRemoteCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
const args = typeof argsOrSha === 'string' ? { sha: argsOrSha } : argsOrSha;
return super.getMarkdownCommandArgsCore<OpenCommitInRemoteCommandArgs>(Commands.OpenCommitInRemote, args);
}
@ -47,10 +44,15 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
const blameline = editor == null ? 0 : editor.selection.active.line;
if (blameline < 0) return undefined;
const blame = editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open commit in remote provider');
const blame =
editor && editor.document && editor.document.isDirty
? await Container.git.getBlameForLineContents(gitUri, blameline, editor.document.getText())
: await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage(
'Unable to open commit in remote provider'
);
}
let commit = blame.commit;
// If the line is uncommitted, find the previous commit
@ -78,7 +80,9 @@ export class OpenCommitInRemoteCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'OpenCommitInRemoteCommand');
return window.showErrorMessage(`Unable to open commit in remote provider. See output channel for more details`);
return window.showErrorMessage(
`Unable to open commit in remote provider. See output channel for more details`
);
}
}
}
}

+ 30
- 10
src/commands/openFileInRemote.ts View File

@ -1,6 +1,13 @@
'use strict';
import { commands, Range, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithBranch, isCommandViewContextWithCommit } from './common';
import {
ActiveEditorCommand,
CommandContext,
Commands,
getCommandUri,
isCommandViewContextWithBranch,
isCommandViewContextWithCommit
} from './common';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitUri } from '../gitService';
@ -14,12 +21,14 @@ export interface OpenFileInRemoteCommandArgs {
}
export class OpenFileInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenFileInRemote);
}
protected async preExecute(context: CommandContext, args: OpenFileInRemoteCommandArgs = { range: true }): Promise<any> {
protected async preExecute(
context: CommandContext,
args: OpenFileInRemoteCommandArgs = { range: true }
): Promise<any> {
if (isCommandViewContextWithCommit(context)) {
args = { ...args };
args.range = false;
@ -42,9 +51,14 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
if (args.branch === undefined) {
const branch = await Container.git.getBranch(gitUri.repoPath);
if (branch === undefined || branch.tracking === undefined) {
const branches = (await Container.git.getBranches(gitUri.repoPath)).filter(b => b.tracking !== undefined);
const branches = (await Container.git.getBranches(gitUri.repoPath)).filter(
b => b.tracking !== undefined
);
if (branches.length > 1) {
const pick = await BranchesQuickPick.show(branches, `Open ${gitUri.getRelativePath()} in remote for which branch${GlyphChars.Ellipsis}`);
const pick = await BranchesQuickPick.show(
branches,
`Open ${gitUri.getRelativePath()} in remote for which branch${GlyphChars.Ellipsis}`
);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return undefined;
@ -62,9 +76,13 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
try {
const remotes = await Container.git.getRemotes(gitUri.repoPath);
const range = (args.range && editor != null)
? new Range(editor.selection.start.with({ line: editor.selection.start.line + 1 }), editor.selection.end.with({ line: editor.selection.end.line + 1 }))
: undefined;
const range =
args.range && editor != null
? new Range(
editor.selection.start.with({ line: editor.selection.start.line + 1 }),
editor.selection.end.with({ line: editor.selection.end.line + 1 })
)
: undefined;
return commands.executeCommand(Commands.OpenInRemote, uri, {
resource: {
@ -79,7 +97,9 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'OpenFileInRemoteCommand');
return window.showErrorMessage(`Unable to open file in remote provider. See output channel for more details`);
return window.showErrorMessage(
`Unable to open file in remote provider. See output channel for more details`
);
}
}
}
}

+ 55
- 25
src/commands/openFileRevision.ts View File

@ -21,10 +21,13 @@ export interface OpenFileRevisionCommandArgs {
}
export class OpenFileRevisionCommand extends ActiveEditorCommand {
static getMarkdownCommandArgs(args: OpenFileRevisionCommandArgs): string;
static getMarkdownCommandArgs(uri: Uri, annotationType?: FileAnnotationType, line?: number): string;
static getMarkdownCommandArgs(argsOrUri: OpenFileRevisionCommandArgs | Uri, annotationType?: FileAnnotationType, line?: number): string {
static getMarkdownCommandArgs(
argsOrUri: OpenFileRevisionCommandArgs | Uri,
annotationType?: FileAnnotationType,
line?: number
): string {
let args: OpenFileRevisionCommandArgs | Uri;
if (argsOrUri instanceof Uri) {
const uri = argsOrUri;
@ -61,46 +64,74 @@ export class OpenFileRevisionCommand extends ActiveEditorCommand {
const gitUri = await GitUri.fromUri(uri);
const placeHolder = `Open ${gitUri.getFormattedPath()}${gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''} in revision ${GlyphChars.Ellipsis}`;
const placeHolder = `Open ${gitUri.getFormattedPath()}${
gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''
} in revision ${GlyphChars.Ellipsis}`;
progressCancellation = FileHistoryQuickPick.showProgress(placeHolder);
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, { maxCount: args.maxCount, ref: gitUri.sha });
if (log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open history compare');
const log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: args.maxCount,
ref: gitUri.sha
});
if (log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to open history compare');
}
if (progressCancellation.token.isCancellationRequested) return undefined;
let previousPageCommand: CommandQuickPickItem | undefined = undefined;
if (log.truncated) {
const npc = new CommandQuickPickItem({
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} newer commits`
}, Commands.OpenFileRevision, [uri, { ...args } as OpenFileRevisionCommandArgs]);
const npc = new CommandQuickPickItem(
{
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} newer commits`
},
Commands.OpenFileRevision,
[uri, { ...args } as OpenFileRevisionCommandArgs]
);
const last = Iterables.last(log.commits.values());
if (last != null) {
previousPageCommand = new CommandQuickPickItem({
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} older commits`
}, Commands.OpenFileRevision, [new GitUri(uri, last), { ...args, nextPageCommand: npc } as OpenFileRevisionCommandArgs]);
previousPageCommand = new CommandQuickPickItem(
{
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${log.maxCount} older commits`
},
Commands.OpenFileRevision,
[new GitUri(uri, last), { ...args, nextPageCommand: npc } as OpenFileRevisionCommandArgs]
);
}
}
const pick = await FileHistoryQuickPick.show(log, gitUri, placeHolder, {
pickerOnly: true,
progressCancellation: progressCancellation,
currentCommand: new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${gitUri.getFormattedPath()}${gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''}`
}, Commands.OpenFileRevision, [uri, { ...args }]),
currentCommand: new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${
GlyphChars.Space
}$(file-text) ${gitUri.getFormattedPath()}${
gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''
}`
},
Commands.OpenFileRevision,
[uri, { ...args }]
),
nextPageCommand: args.nextPageCommand,
previousPageCommand: previousPageCommand,
showAllCommand: log !== undefined && log.truncated
? new CommandQuickPickItem({
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
}, Commands.OpenFileRevision, [uri, { ...args, maxCount: 0 } as OpenFileRevisionCommandArgs])
: undefined
showAllCommand:
log !== undefined && log.truncated
? new CommandQuickPickItem(
{
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
},
Commands.OpenFileRevision,
[uri, { ...args, maxCount: 0 } as OpenFileRevisionCommandArgs]
)
: undefined
});
if (pick === undefined) return undefined;
@ -117,7 +148,6 @@ export class OpenFileRevisionCommand extends ActiveEditorCommand {
args.uri = GitUri.toRevisionUri(pick.commit.sha, pick.commit.uri.fsPath, pick.commit.repoPath);
}
}
if (args.line !== undefined && args.line !== 0) {
@ -140,4 +170,4 @@ export class OpenFileRevisionCommand extends ActiveEditorCommand {
progressCancellation && progressCancellation.cancel();
}
}
}
}

+ 9
- 6
src/commands/openInRemote.ts View File

@ -16,7 +16,6 @@ export interface OpenInRemoteCommandArgs {
}
export class OpenInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenInRemote);
}
@ -60,15 +59,20 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
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)} ${args.resource.commit.previousShortSha} in${GlyphChars.Ellipsis}`;
placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
args.resource.commit.previousShortSha
} in${GlyphChars.Ellipsis}`;
}
else {
args.resource.sha = args.resource.commit.sha;
placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${args.resource.commit.shortSha} in${GlyphChars.Ellipsis}`;
placeHolder = `open ${args.resource.fileName} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${
args.resource.commit.shortSha
} in${GlyphChars.Ellipsis}`;
}
}
else {
const shortFileSha = args.resource.sha === undefined ? '' : GitService.shortenSha(args.resource.sha);
const shortFileSha =
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}`;
@ -85,7 +89,6 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
if (pick === undefined) return undefined;
return await pick.execute();
}
catch (ex) {
Logger.error(ex, 'OpenInRemoteCommand');
@ -107,4 +110,4 @@ export class OpenInRemoteCommand extends ActiveEditorCommand {
}
}
}
}
}

+ 18
- 6
src/commands/openRepoInRemote.ts View File

@ -1,6 +1,13 @@
'use strict';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCommand, CommandContext, Commands, getCommandUri, getRepoPathOrActiveOrPrompt, isCommandViewContextWithRemote } from './common';
import {
ActiveEditorCommand,
CommandContext,
Commands,
getCommandUri,
getRepoPathOrActiveOrPrompt,
isCommandViewContextWithRemote
} from './common';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitUri } from '../gitService';
@ -12,7 +19,6 @@ export interface OpenRepoInRemoteCommandArgs {
}
export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenRepoInRemote);
}
@ -29,9 +35,13 @@ export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
async execute(editor?: TextEditor, uri?: Uri, args: OpenRepoInRemoteCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri);
const gitUri = uri && (await GitUri.fromUri(uri));
const repoPath = await getRepoPathOrActiveOrPrompt(gitUri, editor, `Open which repository in remote${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
gitUri,
editor,
`Open which repository in remote${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
try {
@ -47,7 +57,9 @@ export class OpenRepoInRemoteCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'OpenRepoInRemoteCommand');
return window.showErrorMessage(`Unable to open repository in remote provider. See output channel for more details`);
return window.showErrorMessage(
`Unable to open repository in remote provider. See output channel for more details`
);
}
}
}
}

+ 5
- 3
src/commands/openWorkingFile.ts View File

@ -15,7 +15,6 @@ export interface OpenWorkingFileCommandArgs {
}
export class OpenWorkingFileCommand extends ActiveEditorCommand {
constructor() {
super(Commands.OpenWorkingFile);
}
@ -33,7 +32,10 @@ export class OpenWorkingFileCommand extends ActiveEditorCommand {
args.uri = await GitUri.fromUri(uri);
if (args.uri instanceof GitUri && args.uri.sha) {
const [fileName, repoPath] = await Container.git.findWorkingFileName(args.uri.fsPath, args.uri.repoPath);
const [fileName, repoPath] = await Container.git.findWorkingFileName(
args.uri.fsPath,
args.uri.repoPath
);
if (fileName !== undefined && repoPath !== undefined) {
args.uri = new GitUri(Uri.file(path.resolve(repoPath, fileName)), repoPath);
}
@ -57,4 +59,4 @@ export class OpenWorkingFileCommand extends ActiveEditorCommand {
return window.showErrorMessage(`Unable to open working file. See output channel for more details`);
}
}
}
}

+ 6
- 3
src/commands/resetSuppressedWarnings.ts View File

@ -4,12 +4,15 @@ import { Command, Commands } from './common';
import { configuration } from '../configuration';
export class ResetSuppressedWarningsCommand extends Command {
constructor() {
super(Commands.ResetSuppressedWarnings);
}
async execute() {
await configuration.update(configuration.name('advanced')('messages').value, undefined, ConfigurationTarget.Global);
await configuration.update(
configuration.name('advanced')('messages').value,
undefined,
ConfigurationTarget.Global
);
}
}
}

+ 53
- 29
src/commands/showCommitSearch.ts View File

@ -6,7 +6,11 @@ import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitRepoSearchBy, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, CommitsQuickPick, ShowCommitsSearchInResultsQuickPickItem } from '../quickPicks/quickPicks';
import {
CommandQuickPickItem,
CommitsQuickPick,
ShowCommitsSearchInResultsQuickPickItem
} from '../quickPicks/quickPicks';
import { ShowQuickCommitDetailsCommandArgs } from './showQuickCommitDetails';
const searchByRegex = /^([@~=:#])/;
@ -27,7 +31,6 @@ export interface ShowCommitSearchCommandArgs {
}
export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowCommitSearch);
}
@ -35,9 +38,14 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
async execute(editor?: TextEditor, uri?: Uri, args: ShowCommitSearchCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri);
const gitUri = uri && (await GitUri.fromUri(uri));
const repoPath = await getRepoPathOrActiveOrPrompt(gitUri, editor, `Search for commits in which repository${GlyphChars.Ellipsis}`, args.goBackCommand);
const repoPath = await getRepoPathOrActiveOrPrompt(
gitUri,
editor,
`Search for commits in which repository${GlyphChars.Ellipsis}`,
args.goBackCommand
);
if (!repoPath) return undefined;
args = { ...args };
@ -63,14 +71,16 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
prompt: `Please enter a search string`,
placeHolder: `search by message, author (@<pattern>), files (:<pattern>), commit id (#<sha>), changes (~<pattern>), or changed occurrences (=<string>)`
} as InputBoxOptions);
if (args.search === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
if (args.search === undefined) {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
originalArgs.search = args.search;
const match = searchByRegex.exec(args.search);
if (match && match[1]) {
args.searchBy = searchByMap.get(match[1]);
args.search = args.search.substring((args.search[1] === ' ') ? 2 : 1);
args.search = args.search.substring(args.search[1] === ' ' ? 2 : 1);
}
else if (GitService.isSha(args.search)) {
args.searchBy = GitRepoSearchBy.Sha;
@ -113,41 +123,55 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
const progressCancellation = CommitsQuickPick.showProgress(searchLabel!);
try {
const log = await Container.git.getLogForSearch(repoPath, args.search, args.searchBy, { maxCount: args.maxCount });
const log = await Container.git.getLogForSearch(repoPath, args.search, args.searchBy, {
maxCount: args.maxCount
});
if (progressCancellation.token.isCancellationRequested) return undefined;
const goBackCommand = args.goBackCommand || new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to commit search`
}, Commands.ShowCommitSearch, [uri, originalArgs]);
const goBackCommand =
args.goBackCommand ||
new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to commit search`
},
Commands.ShowCommitSearch,
[uri, originalArgs]
);
const pick = await CommitsQuickPick.show(log, searchLabel!, progressCancellation, {
goBackCommand: goBackCommand,
showAllCommand: log !== undefined && log.truncated
? new CommandQuickPickItem({
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
}, Commands.ShowCommitSearch, [uri, { ...args, maxCount: 0, goBackCommand: goBackCommand }])
: undefined,
showInResultsExplorerCommand: log !== undefined
? new ShowCommitsSearchInResultsQuickPickItem(log, searchLabel!)
: undefined
showAllCommand:
log !== undefined && log.truncated
? new CommandQuickPickItem(
{
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
},
Commands.ShowCommitSearch,
[uri, { ...args, maxCount: 0, goBackCommand: goBackCommand }]
)
: undefined,
showInResultsExplorerCommand:
log !== undefined ? new ShowCommitsSearchInResultsQuickPickItem(log, searchLabel!) : undefined
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitDetails,
pick.commit.toGitUri(),
{
sha: pick.commit.sha,
commit: pick.commit,
goBackCommand: new CommandQuickPickItem({
return commands.executeCommand(Commands.ShowQuickCommitDetails, pick.commit.toGitUri(), {
sha: pick.commit.sha,
commit: pick.commit,
goBackCommand: new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 2)} to search for ${searchLabel}`
}, Commands.ShowCommitSearch, [ uri, args ])
} as ShowQuickCommitDetailsCommandArgs);
},
Commands.ShowCommitSearch,
[uri, args]
)
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowCommitSearchCommand');
@ -157,4 +181,4 @@ export class ShowCommitSearchCommand extends ActiveEditorCachedCommand {
progressCancellation.cancel();
}
}
}
}

+ 1
- 2
src/commands/showGitExplorer.ts View File

@ -4,7 +4,6 @@ import { GitExplorerView } from '../configuration';
import { Container } from '../container';
export class ShowGitExplorerCommand extends Command {
constructor() {
super(Commands.ShowGitExplorer);
}
@ -12,4 +11,4 @@ export class ShowGitExplorerCommand extends Command {
execute() {
return Container.gitExplorer.show(GitExplorerView.Repository);
}
}
}

+ 1
- 2
src/commands/showHistoryExplorer.ts View File

@ -4,7 +4,6 @@ import { GitExplorerView } from '../configuration';
import { Container } from '../container';
export class ShowHistoryExplorerCommand extends Command {
constructor() {
super(Commands.ShowHistoryExplorer);
}
@ -16,4 +15,4 @@ export class ShowHistoryExplorerCommand extends Command {
return Container.gitExplorer.show(GitExplorerView.History);
}
}
}

+ 1
- 2
src/commands/showLastQuickPick.ts View File

@ -4,7 +4,6 @@ import { Command, Commands, getLastCommand } from './common';
import { Logger } from '../logger';
export class ShowLastQuickPickCommand extends Command {
constructor() {
super(Commands.ShowLastQuickPick);
}
@ -21,4 +20,4 @@ export class ShowLastQuickPickCommand extends Command {
return window.showErrorMessage(`Unable to show last quick pick. See output channel for more details`);
}
}
}
}

+ 53
- 25
src/commands/showQuickBranchHistory.ts View File

@ -20,7 +20,6 @@ export interface ShowQuickBranchHistoryCommandArgs {
}
export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowQuickBranchHistory);
}
@ -28,13 +27,20 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
async execute(editor?: TextEditor, uri?: Uri, args: ShowQuickBranchHistoryCommandArgs = {}) {
uri = getCommandUri(uri, editor);
const gitUri = uri && await GitUri.fromUri(uri);
const gitUri = uri && (await GitUri.fromUri(uri));
args = { ...args };
let progressCancellation = args.branch === undefined ? undefined : BranchHistoryQuickPick.showProgress(args.branch);
let progressCancellation =
args.branch === undefined ? undefined : BranchHistoryQuickPick.showProgress(args.branch);
try {
const repoPath = args.repoPath || await getRepoPathOrActiveOrPrompt(gitUri, editor, `Show branch history in which repository${GlyphChars.Ellipsis}`);
const repoPath =
args.repoPath ||
(await getRepoPathOrActiveOrPrompt(
gitUri,
editor,
`Show branch history in which repository${GlyphChars.Ellipsis}`
));
if (!repoPath) return undefined;
if (args.branch === undefined) {
@ -42,13 +48,19 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
let goBackCommand;
if (!(await Container.git.getRepoPathOrActive(uri, editor))) {
goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to which repository`
}, Commands.ShowQuickBranchHistory, [uri, args]);
goBackCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to which repository`
},
Commands.ShowQuickBranchHistory,
[uri, args]
);
}
const pick = await BranchesQuickPick.show(branches, `Show history for branch${GlyphChars.Ellipsis}`, { goBackCommand: goBackCommand });
const pick = await BranchesQuickPick.show(branches, `Show history for branch${GlyphChars.Ellipsis}`, {
goBackCommand: goBackCommand
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
@ -60,31 +72,47 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
}
if (args.log === undefined) {
args.log = await Container.git.getLog(repoPath, { maxCount: args.maxCount, ref: (gitUri && gitUri.sha) || args.branch });
args.log = await Container.git.getLog(repoPath, {
maxCount: args.maxCount,
ref: (gitUri && gitUri.sha) || args.branch
});
if (args.log === undefined) return window.showWarningMessage(`Unable to show branch history`);
}
if (progressCancellation !== undefined && progressCancellation.token.isCancellationRequested) return undefined;
if (progressCancellation !== undefined && progressCancellation.token.isCancellationRequested) {
return undefined;
}
const pick = await BranchHistoryQuickPick.show(args.log, gitUri, args.branch, progressCancellation!, args.goBackCommand, args.nextPageCommand);
const pick = await BranchHistoryQuickPick.show(
args.log,
gitUri,
args.branch,
progressCancellation!,
args.goBackCommand,
args.nextPageCommand
);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
// Create a command to get back to here
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${GlyphChars.Space}$(git-branch) ${args.branch} history`
}, Commands.ShowQuickBranchHistory, [uri, { ...args }]);
return commands.executeCommand(Commands.ShowQuickCommitDetails,
pick.commit.toGitUri(),
const currentCommand = new CommandQuickPickItem(
{
sha: pick.commit.sha,
commit: pick.commit,
repoLog: args.log,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${GlyphChars.Space}$(git-branch) ${
args.branch
} history`
},
Commands.ShowQuickBranchHistory,
[uri, { ...args }]
);
return commands.executeCommand(Commands.ShowQuickCommitDetails, pick.commit.toGitUri(), {
sha: pick.commit.sha,
commit: pick.commit,
repoLog: args.log,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickBranchHistoryCommand');
@ -94,4 +122,4 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand {
progressCancellation && progressCancellation.cancel();
}
}
}
}

+ 56
- 33
src/commands/showQuickCommitDetails.ts View File

@ -1,7 +1,13 @@
'use strict';
import { Strings } from '../system';
import { commands, TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import {
ActiveEditorCachedCommand,
CommandContext,
Commands,
getCommandUri,
isCommandViewContextWithCommit
} from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitUri } from '../gitService';
import { Logger } from '../logger';
@ -20,14 +26,14 @@ export interface ShowQuickCommitDetailsCommandArgs {
}
export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitDetailsCommandArgs>(Commands.ShowQuickCommitDetails, args);
const args = typeof argsOrSha === 'string' ? { sha: argsOrSha } : argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitDetailsCommandArgs>(
Commands.ShowQuickCommitDetails,
args
);
}
constructor() {
@ -64,10 +70,14 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit details');
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit details');
}
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit details');
if (blame.commit.isUncommitted) {
return Messages.showLineUncommittedWarningMessage('Unable to show commit details');
}
args.sha = blame.commit.sha;
repoPath = blame.commit.repoPath;
@ -93,13 +103,17 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
if (args.repoLog === undefined) {
const log = await Container.git.getLog(repoPath!, { maxCount: 2, ref: args.sha });
if (log === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
if (log === undefined) {
return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
}
args.commit = log.commits.get(args.sha!);
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
if (args.commit === undefined) {
return Messages.showCommitNotFoundWarningMessage(`Unable to show commit details`);
}
if (args.commit.workingFileName === undefined) {
args.commit.workingFileName = workingFileName;
@ -109,40 +123,49 @@ export class ShowQuickCommitDetailsCommand extends ActiveEditorCachedCommand {
const branch = await Container.git.getBranch(args.commit.repoPath);
if (branch !== undefined) {
// Create a command to get back to the branch history
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${branch.name} history`
}, Commands.ShowQuickCurrentBranchHistory, [
args.commit.toGitUri()
]);
args.goBackCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to ${branch.name} history`
},
Commands.ShowQuickCurrentBranchHistory,
[args.commit.toGitUri()]
);
}
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${args.commit.shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.repoLog);
const currentCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${
GlyphChars.Space
}$(git-commit) ${args.commit.shortSha}`
},
Commands.ShowQuickCommitDetails,
[args.commit.toGitUri(), args]
);
const pick = await CommitQuickPick.show(
args.commit as GitLogCommit,
uri,
args.goBackCommand,
currentCommand,
args.repoLog
);
if (pick === undefined) return undefined;
if (!(pick instanceof CommitWithFileStatusQuickPickItem)) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
pick.commit.toGitUri(),
{
commit: pick.commit,
sha: pick.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
return commands.executeCommand(Commands.ShowQuickCommitFileDetails, pick.commit.toGitUri(), {
commit: pick.commit,
sha: pick.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitDetailsCommand');
return window.showErrorMessage(`Unable to show commit details. See output channel for more details`);
}
}
}
}

+ 68
- 28
src/commands/showQuickCommitFileDetails.ts View File

@ -1,7 +1,13 @@
'use strict';
import { Strings } from '../system';
import { TextEditor, Uri, window } from 'vscode';
import { ActiveEditorCachedCommand, CommandContext, Commands, getCommandUri, isCommandViewContextWithCommit } from './common';
import {
ActiveEditorCachedCommand,
CommandContext,
Commands,
getCommandUri,
isCommandViewContextWithCommit
} from './common';
import { GlyphChars } from '../constants';
import { GitCommit, GitLog, GitLogCommit, GitService, GitUri } from '../gitService';
import { Logger } from '../logger';
@ -20,21 +26,24 @@ export interface ShowQuickCommitFileDetailsCommandArgs {
}
export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand {
static getMarkdownCommandArgs(sha: string): string;
static getMarkdownCommandArgs(args: ShowQuickCommitFileDetailsCommandArgs): string;
static getMarkdownCommandArgs(argsOrSha: ShowQuickCommitFileDetailsCommandArgs | string): string {
const args = typeof argsOrSha === 'string'
? { sha: argsOrSha }
: argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitFileDetailsCommandArgs>(Commands.ShowQuickCommitFileDetails, args);
const args = typeof argsOrSha === 'string' ? { sha: argsOrSha } : argsOrSha;
return super.getMarkdownCommandArgsCore<ShowQuickCommitFileDetailsCommandArgs>(
Commands.ShowQuickCommitFileDetails,
args
);
}
constructor() {
super(Commands.ShowQuickCommitFileDetails);
}
protected async preExecute(context: CommandContext, args: ShowQuickCommitFileDetailsCommandArgs = {}): Promise<any> {
protected async preExecute(
context: CommandContext,
args: ShowQuickCommitFileDetailsCommandArgs = {}
): Promise<any> {
if (context.type === 'view') {
args = { ...args };
args.sha = context.node.uri.sha;
@ -63,10 +72,14 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
try {
const blame = await Container.git.getBlameForLine(gitUri, blameline);
if (blame === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit file details');
if (blame === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show commit file details');
}
// Because the previous sha of an uncommitted file isn't trust worthy we just have to kick out
if (blame.commit.isUncommitted) return Messages.showLineUncommittedWarningMessage('Unable to show commit file details');
if (blame.commit.isUncommitted) {
return Messages.showLineUncommittedWarningMessage('Unable to show commit file details');
}
args.sha = blame.commit.sha;
@ -75,7 +88,9 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
}
catch (ex) {
Logger.error(ex, 'ShowQuickCommitFileDetailsCommand', `getBlameForLine(${blameline})`);
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
return window.showErrorMessage(
`Unable to show commit file details. See output channel for more details`
);
}
}
@ -94,12 +109,20 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
}
if (args.fileLog === undefined) {
args.commit = await Container.git.getLogCommitForFile(args.commit === undefined ? gitUri.repoPath : args.commit.repoPath, gitUri.fsPath, { ref: args.sha });
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
args.commit = await Container.git.getLogCommitForFile(
args.commit === undefined ? gitUri.repoPath : args.commit.repoPath,
gitUri.fsPath,
{ ref: args.sha }
);
if (args.commit === undefined) {
return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
}
}
}
if (args.commit === undefined) return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
if (args.commit === undefined) {
return Messages.showCommitNotFoundWarningMessage(`Unable to show commit file details`);
}
// Attempt to the most recent commit -- so that we can find the real working filename if there was a rename
args.commit.workingFileName = workingFileName;
@ -109,28 +132,45 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
if (args.goBackCommand === undefined) {
// Create a command to get back to the commit details
args.goBackCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitDetails, [
args.goBackCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${
GlyphChars.Space
}$(git-commit) ${shortSha}`
},
Commands.ShowQuickCommitDetails,
[
args.commit.toGitUri(),
{
commit: args.commit,
sha: args.sha
} as ShowQuickCommitDetailsCommandArgs
]);
]
);
}
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${GlyphChars.Space}$(file-text) ${path.basename(args.commit.fileName)} in ${GlyphChars.Space}$(git-commit) ${shortSha}`
}, Commands.ShowQuickCommitFileDetails, [
args.commit.toGitUri(),
args
]);
const pick = await CommitFileQuickPick.show(args.commit as GitLogCommit, uri, args.goBackCommand, currentCommand, args.fileLog);
const currentCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to details of ${
GlyphChars.Space
}$(file-text) ${path.basename(args.commit.fileName)} in ${
GlyphChars.Space
}$(git-commit) ${shortSha}`
},
Commands.ShowQuickCommitFileDetails,
[args.commit.toGitUri(), args]
);
const pick = await CommitFileQuickPick.show(
args.commit as GitLogCommit,
uri,
args.goBackCommand,
currentCommand,
args.fileLog
);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
@ -142,4 +182,4 @@ export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCachedCommand
return window.showErrorMessage(`Unable to show commit file details. See output channel for more details`);
}
}
}
}

+ 11
- 10
src/commands/showQuickCurrentBranchHistory.ts View File

@ -12,7 +12,6 @@ export interface ShowQuickCurrentBranchHistoryCommandArgs {
}
export class ShowQuickCurrentBranchHistoryCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowQuickCurrentBranchHistory);
}
@ -21,23 +20,25 @@ export class ShowQuickCurrentBranchHistoryCommand extends ActiveEditorCachedComm
uri = getCommandUri(uri, editor);
try {
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Show current branch history for which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Show current branch history for which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const branch = await Container.git.getBranch(repoPath);
if (branch === undefined) return undefined;
return commands.executeCommand(Commands.ShowQuickBranchHistory,
uri,
{
branch: branch.name,
repoPath: repoPath,
goBackCommand: args.goBackCommand
} as ShowQuickBranchHistoryCommandArgs);
return commands.executeCommand(Commands.ShowQuickBranchHistory, uri, {
branch: branch.name,
repoPath: repoPath,
goBackCommand: args.goBackCommand
} as ShowQuickBranchHistoryCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickCurrentBranchHistoryCommand');
return window.showErrorMessage(`Unable to show branch history. See output channel for more details`);
}
}
}
}

+ 74
- 41
src/commands/showQuickFileHistory.ts View File

@ -6,7 +6,11 @@ import { GlyphChars } from '../constants';
import { Container } from '../container';
import { GitLog, GitUri } from '../gitService';
import { Logger } from '../logger';
import { CommandQuickPickItem, FileHistoryQuickPick, ShowCommitsInResultsQuickPickItem } from '../quickPicks/quickPicks';
import {
CommandQuickPickItem,
FileHistoryQuickPick,
ShowCommitsInResultsQuickPickItem
} from '../quickPicks/quickPicks';
import { ShowQuickCommitFileDetailsCommandArgs } from './showQuickCommitFileDetails';
import { Messages } from '../messages';
import * as path from 'path';
@ -21,7 +25,6 @@ export interface ShowQuickFileHistoryCommandArgs {
}
export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowQuickFileHistory);
}
@ -34,13 +37,21 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
args = { ...args };
const placeHolder = `${gitUri.getFormattedPath()}${gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''}`;
const placeHolder = `${gitUri.getFormattedPath()}${
gitUri.sha ? ` ${Strings.pad(GlyphChars.Dot, 1, 1)} ${gitUri.shortSha}` : ''
}`;
const progressCancellation = FileHistoryQuickPick.showProgress(placeHolder);
try {
if (args.log === undefined) {
args.log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, { maxCount: args.maxCount, range: args.range, ref: gitUri.sha });
if (args.log === undefined) return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history');
args.log = await Container.git.getLogForFile(gitUri.repoPath, gitUri.fsPath, {
maxCount: args.maxCount,
range: args.range,
ref: gitUri.sha
});
if (args.log === undefined) {
return Messages.showFileNotUnderSourceControlWarningMessage('Unable to show file history');
}
}
if (progressCancellation.token.isCancellationRequested) return undefined;
@ -48,17 +59,30 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
let previousPageCommand: CommandQuickPickItem | undefined = undefined;
if (args.log.truncated) {
const npc = new CommandQuickPickItem({
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${args.log.maxCount} newer commits`
}, Commands.ShowQuickFileHistory, [gitUri, { ...args, log: undefined } as ShowQuickFileHistoryCommandArgs]);
const npc = new CommandQuickPickItem(
{
label: `$(arrow-right) Show Next Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${args.log.maxCount} newer commits`
},
Commands.ShowQuickFileHistory,
[gitUri, { ...args, log: undefined } as ShowQuickFileHistoryCommandArgs]
);
const last = Iterables.last(args.log.commits.values());
if (last != null) {
previousPageCommand = new CommandQuickPickItem({
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${args.log.maxCount} older commits`
}, Commands.ShowQuickFileHistory, [new GitUri(uri, last), { ...args, log: undefined, nextPageCommand: npc } as ShowQuickFileHistoryCommandArgs]);
previousPageCommand = new CommandQuickPickItem(
{
label: `$(arrow-left) Show Previous Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} shows ${
args.log.maxCount
} older commits`
},
Commands.ShowQuickFileHistory,
[
new GitUri(uri, last),
{ ...args, log: undefined, nextPageCommand: npc } as ShowQuickFileHistoryCommandArgs
]
);
}
}
@ -67,40 +91,49 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
goBackCommand: args.goBackCommand,
nextPageCommand: args.nextPageCommand,
previousPageCommand: previousPageCommand,
showAllCommand: args.log !== undefined && args.log.truncated
? new CommandQuickPickItem({
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
}, Commands.ShowQuickFileHistory, [uri, { ...args, log: undefined, maxCount: 0 }])
: undefined,
showInResultsExplorerCommand: args.log !== undefined
? new ShowCommitsInResultsQuickPickItem(args.log, {
label: placeHolder,
resultsType: { singular: 'commit', plural: 'commits' }
})
: undefined
showAllCommand:
args.log !== undefined && args.log.truncated
? new CommandQuickPickItem(
{
label: `$(sync) Show All Commits`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} this may take a while`
},
Commands.ShowQuickFileHistory,
[uri, { ...args, log: undefined, maxCount: 0 }]
)
: undefined,
showInResultsExplorerCommand:
args.log !== undefined
? new ShowCommitsInResultsQuickPickItem(args.log, {
label: placeHolder,
resultsType: { singular: 'commit', plural: 'commits' }
})
: undefined
});
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
// Create a command to get back to where we are right now
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${GlyphChars.Space}$(file-text) ${path.basename(pick.commit.fileName)}${gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''}`
}, Commands.ShowQuickFileHistory, [
uri,
args
]);
return commands.executeCommand(Commands.ShowQuickCommitFileDetails,
pick.commit.toGitUri(),
const currentCommand = new CommandQuickPickItem(
{
commit: pick.commit,
fileLog: args.log,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to history of ${
GlyphChars.Space
}$(file-text) ${path.basename(pick.commit.fileName)}${
gitUri.sha ? ` from ${GlyphChars.Space}$(git-commit) ${gitUri.shortSha}` : ''
}`
},
Commands.ShowQuickFileHistory,
[uri, args]
);
return commands.executeCommand(Commands.ShowQuickCommitFileDetails, pick.commit.toGitUri(), {
commit: pick.commit,
fileLog: args.log,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitFileDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickFileHistoryCommand');
@ -110,4 +143,4 @@ export class ShowQuickFileHistoryCommand extends ActiveEditorCachedCommand {
progressCancellation.cancel();
}
}
}
}

+ 6
- 3
src/commands/showQuickRepoStatus.ts View File

@ -11,7 +11,6 @@ export interface ShowQuickRepoStatusCommandArgs {
}
export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowQuickRepoStatus);
}
@ -20,7 +19,11 @@ export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
uri = getCommandUri(uri, editor);
try {
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Show status for which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Show status for which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const status = await Container.git.getStatusForRepo(repoPath);
@ -38,4 +41,4 @@ export class ShowQuickRepoStatusCommand extends ActiveEditorCachedCommand {
return window.showErrorMessage(`Unable to show repository status. See output channel for more details`);
}
}
}
}

+ 27
- 16
src/commands/showQuickStashList.ts View File

@ -13,7 +13,6 @@ export interface ShowQuickStashListCommandArgs {
}
export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
constructor() {
super(Commands.ShowQuickStashList);
}
@ -24,7 +23,11 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
const progressCancellation = StashListQuickPick.showProgress('list');
try {
const repoPath = await getRepoPathOrActiveOrPrompt(uri, editor, `Show stashed changes for which repository${GlyphChars.Ellipsis}`);
const repoPath = await getRepoPathOrActiveOrPrompt(
uri,
editor,
`Show stashed changes for which repository${GlyphChars.Ellipsis}`
);
if (!repoPath) return undefined;
const stash = await Container.git.getStashList(repoPath);
@ -33,28 +36,36 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
if (progressCancellation.token.isCancellationRequested) return undefined;
// Create a command to get back to here
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to stashed changes`
}, Commands.ShowQuickStashList, [
const currentCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to stashed changes`
},
Commands.ShowQuickStashList,
[
uri,
{
goBackCommand: args.goBackCommand
} as ShowQuickStashListCommandArgs
]);
]
);
const pick = await StashListQuickPick.show(stash, 'list', progressCancellation, args.goBackCommand, currentCommand);
const pick = await StashListQuickPick.show(
stash,
'list',
progressCancellation,
args.goBackCommand,
currentCommand
);
if (pick === undefined) return undefined;
if (pick instanceof CommandQuickPickItem) return pick.execute();
return commands.executeCommand(Commands.ShowQuickCommitDetails,
pick.commit.toGitUri(),
{
commit: pick.commit,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
return commands.executeCommand(Commands.ShowQuickCommitDetails, pick.commit.toGitUri(), {
commit: pick.commit,
sha: pick.commit.sha,
goBackCommand: currentCommand
} as ShowQuickCommitDetailsCommandArgs);
}
catch (ex) {
Logger.error(ex, 'ShowQuickStashListCommand');
@ -64,4 +75,4 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand {
progressCancellation.cancel();
}
}
}
}

+ 1
- 2
src/commands/showResultsExplorer.ts View File

@ -3,7 +3,6 @@ import { Command, Commands } from './common';
import { Container } from '../container';
export class ShowResultsExplorerCommand extends Command {
constructor() {
super(Commands.ShowResultsExplorer);
}
@ -11,4 +10,4 @@ export class ShowResultsExplorerCommand extends Command {
execute() {
return Container.resultsExplorer.show();
}
}
}

+ 57
- 22
src/commands/stashApply.ts View File

@ -11,18 +11,20 @@ import { CommandQuickPickItem, RepositoriesQuickPick, StashListQuickPick } from
export interface StashApplyCommandArgs {
confirm?: boolean;
deleteAfter?: boolean;
stashItem?: { stashName: string, message: string, repoPath: string };
stashItem?: { stashName: string; message: string; repoPath: string };
goBackCommand?: CommandQuickPickItem;
}
export class StashApplyCommand extends Command {
constructor() {
super(Commands.StashApply);
}
protected async preExecute(context: CommandContext, args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) {
protected async preExecute(
context: CommandContext,
args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }
) {
if (isCommandViewContextWithCommit<GitStashCommit>(context)) {
args = { ...args };
args.stashItem = context.node.commit;
@ -40,14 +42,23 @@ export class StashApplyCommand extends Command {
let repoPath = await Container.git.getActiveRepoPath();
if (!repoPath) {
const pick = await RepositoriesQuickPick.show(`Apply stashed changes from which repository${GlyphChars.Ellipsis}`, args.goBackCommand);
const pick = await RepositoriesQuickPick.show(
`Apply stashed changes from which repository${GlyphChars.Ellipsis}`,
args.goBackCommand
);
if (pick instanceof CommandQuickPickItem) return pick.execute();
if (pick === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
goBackToRepositoriesCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to pick another repository`
}, Commands.StashApply, [args]);
if (pick === undefined) {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
goBackToRepositoriesCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to pick another repository`
},
Commands.StashApply,
[args]
);
repoPath = pick.repoPath;
}
@ -60,14 +71,26 @@ export class StashApplyCommand extends Command {
if (progressCancellation.token.isCancellationRequested) return undefined;
const currentCommand = new CommandQuickPickItem({
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes`
}, Commands.StashApply, [args]);
const pick = await StashListQuickPick.show(stash, 'apply', progressCancellation, goBackToRepositoriesCommand || args.goBackCommand, currentCommand);
const currentCommand = new CommandQuickPickItem(
{
label: `go back ${GlyphChars.ArrowBack}`,
description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes`
},
Commands.StashApply,
[args]
);
const pick = await StashListQuickPick.show(
stash,
'apply',
progressCancellation,
goBackToRepositoriesCommand || args.goBackCommand,
currentCommand
);
if (pick instanceof CommandQuickPickItem) return pick.execute();
if (pick === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
if (pick === undefined) {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
args.goBackCommand = currentCommand;
args.stashItem = pick.commit as GitStashCommit;
@ -79,9 +102,19 @@ export class StashApplyCommand extends Command {
try {
if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message;
const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
const message =
args.stashItem.message.length > 80
? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}`
: args.stashItem.message;
const result = await window.showWarningMessage(
`Apply stashed changes '${message}' to your working tree?`,
{ title: 'Yes, delete after applying' } as MessageItem,
{ title: 'Yes' } as MessageItem,
{ title: 'No', isCloseAffordance: true } as MessageItem
);
if (result === undefined || result.title === 'No') {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
args.deleteAfter = result.title !== 'Yes';
}
@ -91,7 +124,9 @@ export class StashApplyCommand extends Command {
catch (ex) {
Logger.error(ex, 'StashApplyCommand');
if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) {
return window.showWarningMessage(`Unable to apply stash. Your working tree changes would be overwritten.`);
return window.showWarningMessage(
`Unable to apply stash. Your working tree changes would be overwritten.`
);
}
else if (ex.message.includes('Auto-merging') && ex.message.includes('CONFLICT')) {
return window.showInformationMessage(`Stash applied with conflicts`);
@ -101,4 +136,4 @@ export class StashApplyCommand extends Command {
}
}
}
}
}

+ 21
- 7
src/commands/stashDelete.ts View File

@ -9,13 +9,12 @@ import { CommandQuickPickItem } from '../quickPicks/quickPicks';
export interface StashDeleteCommandArgs {
confirm?: boolean;
stashItem?: { stashName: string, message: string, repoPath: string };
stashItem?: { stashName: string; message: string; repoPath: string };
goBackCommand?: CommandQuickPickItem;
}
export class StashDeleteCommand extends Command {
constructor() {
super(Commands.StashDelete);
}
@ -32,7 +31,13 @@ export class StashDeleteCommand extends Command {
async execute(args: StashDeleteCommandArgs = { confirm: true }) {
args = { ...args };
if (args.stashItem === undefined || args.stashItem.stashName === undefined || args.stashItem.repoPath === undefined) return undefined;
if (
args.stashItem === undefined ||
args.stashItem.stashName === undefined ||
args.stashItem.repoPath === undefined
) {
return undefined;
}
if (args.confirm === undefined) {
args.confirm = true;
@ -40,9 +45,18 @@ export class StashDeleteCommand extends Command {
try {
if (args.confirm) {
const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message;
const result = await window.showWarningMessage(`Delete stashed changes '${message}'?`, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem);
if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
const message =
args.stashItem.message.length > 80
? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}`
: args.stashItem.message;
const result = await window.showWarningMessage(
`Delete stashed changes '${message}'?`,
{ title: 'Yes' } as MessageItem,
{ title: 'No', isCloseAffordance: true } as MessageItem
);
if (result === undefined || result.title !== 'Yes') {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
}
return await Container.git.stashDelete(args.stashItem.repoPath, args.stashItem.stashName);
@ -52,4 +66,4 @@ export class StashDeleteCommand extends Command {
return window.showErrorMessage(`Unable to delete stash. See output channel for more details`);
}
}
}
}

+ 12
- 5
src/commands/stashSave.ts View File

@ -15,7 +15,6 @@ export interface StashSaveCommandArgs {
}
export class StashSaveCommand extends Command {
constructor() {
super(Commands.StashSave);
}
@ -29,7 +28,10 @@ export class StashSaveCommand extends Command {
if (context.type === 'scm-groups') {
args = { ...args };
args.uris = context.scmResourceGroups.reduce<Uri[]>((a, b) => a.concat(b.resourceStates.map(s => s.resourceUri)), []);
args.uris = context.scmResourceGroups.reduce<Uri[]>(
(a, b) => a.concat(b.resourceStates.map(s => s.resourceUri)),
[]
);
return this.execute(args);
}
@ -39,7 +41,10 @@ export class StashSaveCommand extends Command {
async execute(args: StashSaveCommandArgs = {}) {
let repoPath = await Container.git.getHighlanderRepoPath();
if (!repoPath) {
const pick = await RepositoriesQuickPick.show(`Stash changes for which repository${GlyphChars.Ellipsis}`, args.goBackCommand);
const pick = await RepositoriesQuickPick.show(
`Stash changes for which repository${GlyphChars.Ellipsis}`,
args.goBackCommand
);
if (pick instanceof CommandQuickPickItem) return pick.execute();
if (pick === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
@ -53,7 +58,9 @@ export class StashSaveCommand extends Command {
prompt: `Please provide a stash message`,
placeHolder: `Stash message`
} as InputBoxOptions);
if (args.message === undefined) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
if (args.message === undefined) {
return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute();
}
}
return await Container.git.stashSave(repoPath, args.message, args.uris);
@ -68,4 +75,4 @@ export class StashSaveCommand extends Command {
return window.showErrorMessage(`Unable to save stash. See output channel for more details`);
}
}
}
}

+ 0
- 3
src/commands/switchMode.ts View File

@ -6,7 +6,6 @@ import { Container } from '../container';
import { ModesQuickPick } from '../quickPicks/quickPicks';
export class SwitchModeCommand extends Command {
constructor() {
super(Commands.SwitchMode);
}
@ -20,7 +19,6 @@ export class SwitchModeCommand extends Command {
}
export class ToggleReviewModeCommand extends Command {
constructor() {
super(Commands.ToggleReviewMode);
}
@ -34,7 +32,6 @@ export class ToggleReviewModeCommand extends Command {
}
export class ToggleZenModeCommand extends Command {
constructor() {
super(Commands.ToggleZenMode);
}

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

@ -3,7 +3,6 @@ import { Command, Commands } from './common';
import { Container } from '../container';
export class ToggleCodeLensCommand extends Command {
constructor() {
super(Commands.ToggleCodeLens);
}

+ 9
- 4
src/commands/toggleFileBlame.ts View File

@ -12,7 +12,6 @@ export interface ToggleFileBlameCommandArgs {
}
export class ToggleFileBlameCommand extends ActiveEditorCommand {
constructor() {
super(Commands.ToggleFileBlame);
}
@ -35,11 +34,17 @@ export class ToggleFileBlameCommand extends ActiveEditorCommand {
args = { ...args, type: FileAnnotationType.Blame };
}
return Container.fileAnnotations.toggle(editor, args.type!, args.sha !== undefined ? args.sha : editor && editor.selection.active.line);
return Container.fileAnnotations.toggle(
editor,
args.type!,
args.sha !== undefined ? args.sha : editor && editor.selection.active.line
);
}
catch (ex) {
Logger.error(ex, 'ToggleFileBlameCommand');
return window.showErrorMessage(`Unable to toggle file ${args.type} annotations. See output channel for more details`);
return window.showErrorMessage(
`Unable to toggle file ${args.type} annotations. See output channel for more details`
);
}
}
}
}

+ 4
- 3
src/commands/toggleFileHeatmap.ts View File

@ -5,12 +5,13 @@ import { ActiveEditorCommand, Commands } from './common';
import { FileAnnotationType } from '../configuration';
export class ToggleFileHeatmapCommand extends ActiveEditorCommand {
constructor() {
super(Commands.ToggleFileHeatmap);
}
async execute(editor: TextEditor, uri?: Uri): Promise<any> {
commands.executeCommand(Commands.ToggleFileBlame, uri, { type: FileAnnotationType.Heatmap } as ToggleFileBlameCommandArgs);
commands.executeCommand(Commands.ToggleFileBlame, uri, {
type: FileAnnotationType.Heatmap
} as ToggleFileBlameCommandArgs);
}
}
}

+ 4
- 3
src/commands/toggleFileRecentChanges.ts View File

@ -5,12 +5,13 @@ import { ActiveEditorCommand, Commands } from './common';
import { FileAnnotationType } from '../configuration';
export class ToggleFileRecentChangesCommand extends ActiveEditorCommand {
constructor() {
super(Commands.ToggleFileRecentChanges);
}
async execute(editor: TextEditor, uri?: Uri): Promise<any> {
commands.executeCommand(Commands.ToggleFileBlame, uri, { type: FileAnnotationType.RecentChanges } as ToggleFileBlameCommandArgs);
commands.executeCommand(Commands.ToggleFileBlame, uri, {
type: FileAnnotationType.RecentChanges
} as ToggleFileBlameCommandArgs);
}
}
}

+ 4
- 3
src/commands/toggleLineBlame.ts View File

@ -5,7 +5,6 @@ import { Container } from '../container';
import { Logger } from '../logger';
export class ToggleLineBlameCommand extends ActiveEditorCommand {
constructor() {
super(Commands.ToggleLineBlame);
}
@ -16,7 +15,9 @@ export class ToggleLineBlameCommand extends ActiveEditorCommand {
}
catch (ex) {
Logger.error(ex, 'ToggleLineBlameCommand');
return window.showErrorMessage(`Unable to toggle line blame annotations. See output channel for more details`);
return window.showErrorMessage(
`Unable to toggle line blame annotations. See output channel for more details`
);
}
}
}
}

+ 6
- 5
src/comparers.ts View File

@ -6,7 +6,6 @@ abstract class Comparer {
}
class UriComparer extends Comparer<Uri> {
equals(lhs: Uri | undefined, rhs: Uri | undefined) {
if (lhs === rhs) return true;
if (lhs === undefined || rhs === undefined) return false;
@ -16,7 +15,6 @@ class UriComparer extends Comparer {
}
class TextDocumentComparer extends Comparer<TextDocument> {
equals(lhs: TextDocument | undefined, rhs: TextDocument | undefined) {
return lhs === rhs;
// if (lhs === rhs) return true;
@ -27,12 +25,15 @@ class TextDocumentComparer extends Comparer {
}
class TextEditorComparer extends Comparer<TextEditor> {
equals(lhs: TextEditor | undefined, rhs: TextEditor | undefined, options: { useId: boolean, usePosition: boolean } = { useId: false, usePosition: false }) {
equals(
lhs: TextEditor | undefined,
rhs: TextEditor | undefined,
options: { useId: boolean; usePosition: boolean } = { useId: false, usePosition: false }
) {
if (lhs === rhs) return true;
if (lhs === undefined || rhs === undefined) return false;
if (options.usePosition && (lhs.viewColumn !== rhs.viewColumn)) return false;
if (options.usePosition && lhs.viewColumn !== rhs.viewColumn) return false;
if (options.useId && (!lhs.document || !rhs.document)) {
if ((lhs as any).id !== (rhs as any).id) return false;

+ 77
- 17
src/configuration.ts View File

@ -2,7 +2,15 @@
export * from './ui/config';
import { Functions } from './system';
import { ConfigurationChangeEvent, ConfigurationTarget, Event, EventEmitter, ExtensionContext, Uri, workspace } from 'vscode';
import {
ConfigurationChangeEvent,
ConfigurationTarget,
Event,
EventEmitter,
ExtensionContext,
Uri,
workspace
} from 'vscode';
import { IConfig, KeyMap } from './ui/config';
import { CommandContext, extensionId, setCommandContext } from './constants';
import { Container } from './container';
@ -15,9 +23,10 @@ const emptyConfig: any = new Proxy({} as IConfig, {
});
export class Configuration {
static configure(context: ExtensionContext) {
context.subscriptions.push(workspace.onDidChangeConfiguration(configuration.onConfigurationChanged, configuration));
context.subscriptions.push(
workspace.onDidChangeConfiguration(configuration.onConfigurationChanged, configuration)
);
}
private _onDidChange = new EventEmitter<ConfigurationChangeEvent>();
@ -54,8 +63,10 @@ export class Configuration {
setCommandContext(CommandContext.KeyMap, this.get<KeyMap>(section));
}
if (configuration.changed(e, configuration.name('mode').value) ||
configuration.changed(e, configuration.name('modes').value)) {
if (
configuration.changed(e, configuration.name('mode').value) ||
configuration.changed(e, configuration.name('modes').value)
) {
const original = e.affectsConfiguration;
e = {
...e,
@ -78,8 +89,12 @@ export class Configuration {
get<T>(section?: string, resource?: Uri | null, defaultValue?: T) {
return defaultValue === undefined
? workspace.getConfiguration(section === undefined ? undefined : extensionId, resource!).get<T>(section === undefined ? extensionId : section)!
: workspace.getConfiguration(section === undefined ? undefined : extensionId, resource!).get<T>(section === undefined ? extensionId : section, defaultValue)!;
? workspace
.getConfiguration(section === undefined ? undefined : extensionId, resource!)
.get<T>(section === undefined ? extensionId : section)!
: workspace
.getConfiguration(section === undefined ? undefined : extensionId, resource!)
.get<T>(section === undefined ? extensionId : section, defaultValue)!;
}
changed(e: ConfigurationChangeEvent, section: string, resource?: Uri | null) {
@ -91,16 +106,26 @@ export class Configuration {
}
inspect(section?: string, resource?: Uri | null) {
return workspace.getConfiguration(section === undefined ? undefined : extensionId, resource!).inspect(section === undefined ? extensionId : section);
return workspace
.getConfiguration(section === undefined ? undefined : extensionId, resource!)
.inspect(section === undefined ? extensionId : section);
}
async migrate<TFrom, TTo>(from: string, to: string, options: { fallbackValue?: TTo, migrationFn?: (value: TFrom) => TTo } = {}): Promise<boolean> {
async migrate<TFrom, TTo>(
from: string,
to: string,
options: { fallbackValue?: TTo; migrationFn?(value: TFrom): TTo } = {}
): Promise<boolean> {
const inspection = configuration.inspect(from);
if (inspection === undefined) return false;
let migrated = false;
if (inspection.globalValue !== undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(inspection.globalValue as TFrom) : inspection.globalValue, ConfigurationTarget.Global);
await this.update(
to,
options.migrationFn ? options.migrationFn(inspection.globalValue as TFrom) : inspection.globalValue,
ConfigurationTarget.Global
);
migrated = true;
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
@ -112,7 +137,13 @@ export class Configuration {
}
if (inspection.workspaceValue !== undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(inspection.workspaceValue as TFrom) : inspection.workspaceValue, ConfigurationTarget.Workspace);
await this.update(
to,
options.migrationFn
? options.migrationFn(inspection.workspaceValue as TFrom)
: inspection.workspaceValue,
ConfigurationTarget.Workspace
);
migrated = true;
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
@ -124,7 +155,13 @@ export class Configuration {
}
if (inspection.workspaceFolderValue !== undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(inspection.workspaceFolderValue as TFrom) : inspection.workspaceFolderValue, ConfigurationTarget.WorkspaceFolder);
await this.update(
to,
options.migrationFn
? options.migrationFn(inspection.workspaceFolderValue as TFrom)
: inspection.workspaceFolderValue,
ConfigurationTarget.WorkspaceFolder
);
migrated = true;
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
@ -143,14 +180,20 @@ export class Configuration {
return migrated;
}
async migrateIfMissing<TFrom, TTo>(from: string, to: string, options: { migrationFn?: (value: TFrom) => TTo } = {}) {
async migrateIfMissing<TFrom, TTo>(from: string, to: string, options: { migrationFn?(value: TFrom): TTo } = {}) {
const fromInspection = configuration.inspect(from);
if (fromInspection === undefined) return;
const toInspection = configuration.inspect(to);
if (fromInspection.globalValue !== undefined) {
if (toInspection === undefined || toInspection.globalValue === undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(fromInspection.globalValue as TFrom) : fromInspection.globalValue, ConfigurationTarget.Global);
await this.update(
to,
options.migrationFn
? options.migrationFn(fromInspection.globalValue as TFrom)
: fromInspection.globalValue,
ConfigurationTarget.Global
);
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
// try {
@ -163,7 +206,13 @@ export class Configuration {
if (fromInspection.workspaceValue !== undefined) {
if (toInspection === undefined || toInspection.workspaceValue === undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(fromInspection.workspaceValue as TFrom) : fromInspection.workspaceValue, ConfigurationTarget.Workspace);
await this.update(
to,
options.migrationFn
? options.migrationFn(fromInspection.workspaceValue as TFrom)
: fromInspection.workspaceValue,
ConfigurationTarget.Workspace
);
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
// try {
@ -176,7 +225,13 @@ export class Configuration {
if (fromInspection.workspaceFolderValue !== undefined) {
if (toInspection === undefined || toInspection.workspaceFolderValue === undefined) {
await this.update(to, options.migrationFn ? options.migrationFn(fromInspection.workspaceFolderValue as TFrom) : fromInspection.workspaceFolderValue, ConfigurationTarget.WorkspaceFolder);
await this.update(
to,
options.migrationFn
? options.migrationFn(fromInspection.workspaceFolderValue as TFrom)
: fromInspection.workspaceFolderValue,
ConfigurationTarget.WorkspaceFolder
);
// Can't delete the old setting currently because it errors with `Unable to write to User Settings because <setting name> is not a registered configuration`
// if (from !== to) {
// try {
@ -209,7 +264,12 @@ export class Configuration {
await configuration.update(section, value, ConfigurationTarget.Workspace);
}
else {
if (inspect.globalValue === value || (inspect.globalValue === undefined && inspect.defaultValue === value)) return;
if (
inspect.globalValue === value ||
(inspect.globalValue === undefined && inspect.defaultValue === value)
) {
return;
}
await configuration.update(section, value, ConfigurationTarget.Global);
}
}

+ 2
- 11
src/constants.ts View File

@ -57,7 +57,7 @@ export enum DocumentSchemes {
export function getEditorIfActive(document: TextDocument): TextEditor | undefined {
const editor = window.activeTextEditor;
return (editor != null && editor.document === document) ? editor : undefined;
return editor != null && editor.document === document ? editor : undefined;
}
export function isActiveDocument(document: TextDocument): boolean {
@ -112,16 +112,7 @@ export enum GlobalState {
GitLensVersion = 'gitlensVersion'
}
export const ImageExtensions = [
'.png',
'.gif',
'.jpg',
'.jpeg',
'.webp',
'.tif',
'.tiff',
'.bmp'
];
export const ImageExtensions = ['.png', '.gif', '.jpg', '.jpeg', '.webp', '.tif', '.tiff', '.bmp'];
export enum WorkspaceState {
GitExplorerAutoRefresh = 'gitlens:gitExplorer:autoRefresh',

+ 24
- 21
src/container.ts View File

@ -20,55 +20,58 @@ import { StatusBarController } from './statusBarController';
import { WelcomeEditor } from './webviews/welcomeEditor';
export class Container {
static initialize(context: ExtensionContext, config: IConfig) {
this._context = context;
this._config = Container.applyMode(config);
context.subscriptions.push(this._lineTracker = new GitLineTracker());
context.subscriptions.push(this._tracker = new GitDocumentTracker());
context.subscriptions.push(this._git = new GitService());
context.subscriptions.push((this._lineTracker = new GitLineTracker()));
context.subscriptions.push((this._tracker = new GitDocumentTracker()));
context.subscriptions.push((this._git = new GitService()));
// Since there is a bit of a chicken & egg problem with the DocumentTracker and the GitService, initialize the tracker once the GitService is loaded
this._tracker.initialize();
context.subscriptions.push(this._fileAnnotationController = new FileAnnotationController());
context.subscriptions.push(this._lineAnnotationController = new LineAnnotationController());
context.subscriptions.push(this._lineHoverController = new LineHoverController());
context.subscriptions.push(this._statusBarController = new StatusBarController());
context.subscriptions.push(this._codeLensController = new CodeLensController());
context.subscriptions.push(this._keyboard = new Keyboard());
context.subscriptions.push(this._settingsEditor = new SettingsEditor());
context.subscriptions.push(this._welcomeEditor = new WelcomeEditor());
context.subscriptions.push((this._fileAnnotationController = new FileAnnotationController()));
context.subscriptions.push((this._lineAnnotationController = new LineAnnotationController()));
context.subscriptions.push((this._lineHoverController = new LineHoverController()));
context.subscriptions.push((this._statusBarController = new StatusBarController()));
context.subscriptions.push((this._codeLensController = new CodeLensController()));
context.subscriptions.push((this._keyboard = new Keyboard()));
context.subscriptions.push((this._settingsEditor = new SettingsEditor()));
context.subscriptions.push((this._welcomeEditor = new WelcomeEditor()));
if (config.gitExplorer.enabled) {
context.subscriptions.push(this._gitExplorer = new GitExplorer());
context.subscriptions.push((this._gitExplorer = new GitExplorer()));
}
else {
let disposable: Disposable;
disposable = configuration.onDidChange(e => {
if (configuration.changed(e, configuration.name('gitExplorer')('enabled').value)) {
disposable.dispose();
context.subscriptions.push(this._gitExplorer = new GitExplorer());
context.subscriptions.push((this._gitExplorer = new GitExplorer()));
}
});
}
if (config.historyExplorer.enabled) {
context.subscriptions.push(this._historyExplorer = new HistoryExplorer());
context.subscriptions.push((this._historyExplorer = new HistoryExplorer()));
}
else {
let disposable: Disposable;
disposable = configuration.onDidChange(e => {
if (configuration.changed(e, configuration.name('historyExplorer')('enabled').value)) {
disposable.dispose();
context.subscriptions.push(this._historyExplorer = new HistoryExplorer());
context.subscriptions.push((this._historyExplorer = new HistoryExplorer()));
}
});
}
context.subscriptions.push(workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider()));
context.subscriptions.push(languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider()));
context.subscriptions.push(
workspace.registerTextDocumentContentProvider(GitContentProvider.scheme, new GitContentProvider())
);
context.subscriptions.push(
languages.registerCodeLensProvider(GitRevisionCodeLensProvider.selector, new GitRevisionCodeLensProvider())
);
}
private static _codeLensController: CodeLensController;
@ -92,7 +95,7 @@ export class Container {
private static _explorerCommands: ExplorerCommands | undefined;
static get explorerCommands() {
if (this._explorerCommands === undefined) {
this._context.subscriptions.push(this._explorerCommands = new ExplorerCommands());
this._context.subscriptions.push((this._explorerCommands = new ExplorerCommands()));
}
return this._explorerCommands;
}
@ -115,7 +118,7 @@ export class Container {
private static _historyExplorer: HistoryExplorer | undefined;
static get historyExplorer() {
if (this._historyExplorer === undefined) {
this._context.subscriptions.push(this._historyExplorer = new HistoryExplorer());
this._context.subscriptions.push((this._historyExplorer = new HistoryExplorer()));
}
return this._historyExplorer;
@ -144,7 +147,7 @@ export class Container {
private static _resultsExplorer: ResultsExplorer | undefined;
static get resultsExplorer() {
if (this._resultsExplorer === undefined) {
this._context.subscriptions.push(this._resultsExplorer = new ResultsExplorer());
this._context.subscriptions.push((this._resultsExplorer = new ResultsExplorer()));
}
return this._resultsExplorer;

+ 356
- 173
src/extension.ts View File

@ -3,7 +3,17 @@
import { Logger } from './logger';
import { Versions } from './system';
import { commands, ExtensionContext, extensions, window, workspace } from 'vscode';
import { CodeLensLanguageScope, CodeLensScopes, configuration, Configuration, HighlightLocations, IConfig, IMenuConfig, KeyMap, OutputLevel } from './configuration';
import {
CodeLensLanguageScope,
CodeLensScopes,
configuration,
Configuration,
HighlightLocations,
IConfig,
IMenuConfig,
KeyMap,
OutputLevel
} from './configuration';
import { CommandContext, extensionId, extensionQualifiedId, GlobalState, setCommandContext } from './constants';
import { Commands, configureCommands } from './commands';
import { Container } from './container';
@ -50,7 +60,7 @@ export async function activate(context: ExtensionContext) {
gitPath = await (gitExtension.exports as GitApi).getGitPath();
}
}
catch { }
catch {}
}
await GitService.initialize(gitPath || workspace.getConfiguration('git').get<string>('path'));
@ -58,7 +68,11 @@ export async function activate(context: ExtensionContext) {
catch (ex) {
Logger.error(ex, `GitLens(v${gitlensVersion}).activate`);
if (ex.message.includes('Unable to find git')) {
await window.showErrorMessage(`GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that '${extensionId}.${configuration.name('advanced')('git').value}' is pointed to its installed location.`);
await window.showErrorMessage(
`GitLens was unable to find Git. Please make sure Git is installed. Also ensure that Git is either in the PATH, or that '${extensionId}.${
configuration.name('advanced')('git').value
}' is pointed to its installed location.`
);
}
setCommandContext(CommandContext.Enabled, false);
return;
@ -87,10 +101,10 @@ export async function activate(context: ExtensionContext) {
// Telemetry.trackEvent('initialized', Objects.flatten(cfg, 'config', true));
const duration = process.hrtime(start);
Logger.log(`GitLens(v${gitlensVersion}) activated in ${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms`);
Logger.log(`GitLens(v${gitlensVersion}) activated in ${duration[0] * 1000 + Math.floor(duration[1] / 1000000)} ms`);
}
export function deactivate() { }
export function deactivate() {}
async function migrateSettings(context: ExtensionContext, previousVersion: string | undefined) {
if (previousVersion === undefined) return;
@ -99,215 +113,384 @@ async function migrateSettings(context: ExtensionContext, previousVersion: strin
try {
if (Versions.compare(previous, Versions.from(7, 5, 10)) !== 1) {
await configuration.migrate('annotations.file.gutter.gravatars', configuration.name('blame')('avatars').value);
await configuration.migrate('annotations.file.gutter.compact', configuration.name('blame')('compact').value);
await configuration.migrate('annotations.file.gutter.dateFormat', configuration.name('blame')('dateFormat').value);
await configuration.migrate(
'annotations.file.gutter.gravatars',
configuration.name('blame')('avatars').value
);
await configuration.migrate(
'annotations.file.gutter.compact',
configuration.name('blame')('compact').value
);
await configuration.migrate(
'annotations.file.gutter.dateFormat',
configuration.name('blame')('dateFormat').value
);
await configuration.migrate('annotations.file.gutter.format', configuration.name('blame')('format').value);
await configuration.migrate('annotations.file.gutter.heatmap.enabled', configuration.name('blame')('heatmap')('enabled').value);
await configuration.migrate('annotations.file.gutter.heatmap.location', configuration.name('blame')('heatmap')('location').value);
await configuration.migrate('annotations.file.gutter.lineHighlight.enabled', configuration.name('blame')('highlight')('enabled').value);
await configuration.migrate('annotations.file.gutter.lineHighlight.locations', configuration.name('blame')('highlight')('locations').value);
await configuration.migrate('annotations.file.gutter.separateLines', configuration.name('blame')('separateLines').value);
await configuration.migrate(
'annotations.file.gutter.heatmap.enabled',
configuration.name('blame')('heatmap')('enabled').value
);
await configuration.migrate(
'annotations.file.gutter.heatmap.location',
configuration.name('blame')('heatmap')('location').value
);
await configuration.migrate(
'annotations.file.gutter.lineHighlight.enabled',
configuration.name('blame')('highlight')('enabled').value
);
await configuration.migrate(
'annotations.file.gutter.lineHighlight.locations',
configuration.name('blame')('highlight')('locations').value
);
await configuration.migrate(
'annotations.file.gutter.separateLines',
configuration.name('blame')('separateLines').value
);
await configuration.migrate('codeLens.locations', configuration.name('codeLens')('scopes').value);
await configuration.migrate<{ customSymbols?: string[], language: string | undefined, locations: CodeLensScopes[] }[], CodeLensLanguageScope[]>(
'codeLens.perLanguageLocations', configuration.name('codeLens')('scopesByLanguage').value, {
migrationFn: v => {
const scopes = v.map(ls => {
return {
language: ls.language,
scopes: ls.locations,
symbolScopes: ls.customSymbols
};
});
return scopes;
}
});
await configuration.migrate('codeLens.customLocationSymbols', configuration.name('codeLens')('symbolScopes').value);
await configuration.migrate('annotations.line.trailing.dateFormat', configuration.name('currentLine')('dateFormat').value);
await configuration.migrate<
{ customSymbols?: string[]; language: string | undefined; locations: CodeLensScopes[] }[],
CodeLensLanguageScope[]
>('codeLens.perLanguageLocations', configuration.name('codeLens')('scopesByLanguage').value, {
migrationFn: v => {
const scopes = v.map(ls => {
return {
language: ls.language,
scopes: ls.locations,
symbolScopes: ls.customSymbols
};
});
return scopes;
}
});
await configuration.migrate(
'codeLens.customLocationSymbols',
configuration.name('codeLens')('symbolScopes').value
);
await configuration.migrate(
'annotations.line.trailing.dateFormat',
configuration.name('currentLine')('dateFormat').value
);
await configuration.migrate('blame.line.enabled', configuration.name('currentLine')('enabled').value);
await configuration.migrate('annotations.line.trailing.format', configuration.name('currentLine')('format').value);
await configuration.migrate('annotations.file.gutter.hover.changes', configuration.name('hovers')('annotations')('changes').value);
await configuration.migrate('annotations.file.gutter.hover.details', configuration.name('hovers')('annotations')('details').value);
await configuration.migrate('annotations.file.gutter.hover.details', configuration.name('hovers')('annotations')('enabled').value);
await configuration.migrate(
'annotations.line.trailing.format',
configuration.name('currentLine')('format').value
);
await configuration.migrate(
'annotations.file.gutter.hover.changes',
configuration.name('hovers')('annotations')('changes').value
);
await configuration.migrate(
'annotations.file.gutter.hover.details',
configuration.name('hovers')('annotations')('details').value
);
await configuration.migrate(
'annotations.file.gutter.hover.details',
configuration.name('hovers')('annotations')('enabled').value
);
await configuration.migrate<boolean, 'line' | 'annotation'>(
'annotations.file.gutter.hover.wholeLine', configuration.name('hovers')('annotations')('over').value,
{ migrationFn: v => v ? 'line' : 'annotation' });
await configuration.migrate('annotations.line.trailing.hover.changes', configuration.name('hovers')('currentLine')('changes').value);
await configuration.migrate('annotations.line.trailing.hover.details', configuration.name('hovers')('currentLine')('details').value);
await configuration.migrate('blame.line.enabled', configuration.name('hovers')('currentLine')('enabled').value);
'annotations.file.gutter.hover.wholeLine',
configuration.name('hovers')('annotations')('over').value,
{ migrationFn: v => (v ? 'line' : 'annotation') }
);
await configuration.migrate(
'annotations.line.trailing.hover.changes',
configuration.name('hovers')('currentLine')('changes').value
);
await configuration.migrate(
'annotations.line.trailing.hover.details',
configuration.name('hovers')('currentLine')('details').value
);
await configuration.migrate(
'blame.line.enabled',
configuration.name('hovers')('currentLine')('enabled').value
);
await configuration.migrate<boolean, 'line' | 'annotation'>(
'annotations.line.trailing.hover.wholeLine', configuration.name('hovers')('currentLine')('over').value,
{ migrationFn: v => v ? 'line' : 'annotation' });
'annotations.line.trailing.hover.wholeLine',
configuration.name('hovers')('currentLine')('over').value,
{ migrationFn: v => (v ? 'line' : 'annotation') }
);
await configuration.migrate('gitExplorer.gravatars', configuration.name('explorers')('avatars').value);
await configuration.migrate('gitExplorer.commitFileFormat', configuration.name('explorers')('commitFileFormat').value);
await configuration.migrate('gitExplorer.commitFormat', configuration.name('explorers')('commitFormat').value);
await configuration.migrate('gitExplorer.stashFileFormat', configuration.name('explorers')('stashFileFormat').value);
await configuration.migrate('gitExplorer.stashFormat', configuration.name('explorers')('stashFormat').value);
await configuration.migrate('gitExplorer.statusFileFormat', configuration.name('explorers')('statusFileFormat').value);
await configuration.migrate('recentChanges.file.lineHighlight.locations', configuration.name('recentChanges')('highlight')('locations').value);
await configuration.migrate(
'gitExplorer.commitFileFormat',
configuration.name('explorers')('commitFileFormat').value
);
await configuration.migrate(
'gitExplorer.commitFormat',
configuration.name('explorers')('commitFormat').value
);
await configuration.migrate(
'gitExplorer.stashFileFormat',
configuration.name('explorers')('stashFileFormat').value
);
await configuration.migrate(
'gitExplorer.stashFormat',
configuration.name('explorers')('stashFormat').value
);
await configuration.migrate(
'gitExplorer.statusFileFormat',
configuration.name('explorers')('statusFileFormat').value
);
await configuration.migrate(
'recentChanges.file.lineHighlight.locations',
configuration.name('recentChanges')('highlight')('locations').value
);
}
if (Versions.compare(previous, Versions.from(8, 0, 0, 'beta2')) !== 1) {
await configuration.migrate<boolean, OutputLevel>(
'debug', configuration.name('outputLevel').value,
{ migrationFn: v => v ? OutputLevel.Debug : configuration.get<OutputLevel>(configuration.name('outputLevel').value) });
await configuration.migrate<boolean, OutputLevel>('debug', configuration.name('outputLevel').value, {
migrationFn: v =>
v ? OutputLevel.Debug : configuration.get<OutputLevel>(configuration.name('outputLevel').value)
});
await configuration.migrate('debug', configuration.name('debug').value, { migrationFn: v => undefined });
}
if (Versions.compare(previous, Versions.from(8, 0, 0, 'rc')) !== 1) {
let section = configuration.name('blame')('highlight')('locations').value;
await configuration.migrate<('gutter' | 'line' | 'overviewRuler')[], HighlightLocations[]>(section, section, {
migrationFn: v => {
const index = v.indexOf('overviewRuler');
if (index !== -1) {
v.splice(index, 1, 'overview' as 'overviewRuler');
await configuration.migrate<('gutter' | 'line' | 'overviewRuler')[], HighlightLocations[]>(
section,
section,
{
migrationFn: v => {
const index = v.indexOf('overviewRuler');
if (index !== -1) {
v.splice(index, 1, 'overview' as 'overviewRuler');
}
return v as HighlightLocations[];
}
return v as HighlightLocations[];
}
});
);
section = configuration.name('recentChanges')('highlight')('locations').value;
await configuration.migrate<('gutter' | 'line' | 'overviewRuler')[], HighlightLocations[]>(section, section, {
migrationFn: v => {
const index = v.indexOf('overviewRuler');
if (index !== -1) {
v.splice(index, 1, 'overview' as 'overviewRuler');
await configuration.migrate<('gutter' | 'line' | 'overviewRuler')[], HighlightLocations[]>(
section,
section,
{
migrationFn: v => {
const index = v.indexOf('overviewRuler');
if (index !== -1) {
v.splice(index, 1, 'overview' as 'overviewRuler');
}
return v as HighlightLocations[];
}
return v as HighlightLocations[];
}
});
);
}
if (Versions.compare(previous, Versions.from(8, 0, 0)) !== 1) {
await configuration.migrateIfMissing('annotations.file.gutter.gravatars', configuration.name('blame')('avatars').value);
await configuration.migrateIfMissing('annotations.file.gutter.compact', configuration.name('blame')('compact').value);
await configuration.migrateIfMissing('annotations.file.gutter.dateFormat', configuration.name('blame')('dateFormat').value);
await configuration.migrateIfMissing('annotations.file.gutter.format', configuration.name('blame')('format').value);
await configuration.migrateIfMissing('annotations.file.gutter.heatmap.enabled', configuration.name('blame')('heatmap')('enabled').value);
await configuration.migrateIfMissing('annotations.file.gutter.heatmap.location', configuration.name('blame')('heatmap')('location').value);
await configuration.migrateIfMissing('annotations.file.gutter.lineHighlight.enabled', configuration.name('blame')('highlight')('enabled').value);
await configuration.migrateIfMissing('annotations.file.gutter.lineHighlight.locations', configuration.name('blame')('highlight')('locations').value);
await configuration.migrateIfMissing('annotations.file.gutter.separateLines', configuration.name('blame')('separateLines').value);
await configuration.migrateIfMissing(
'annotations.file.gutter.gravatars',
configuration.name('blame')('avatars').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.compact',
configuration.name('blame')('compact').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.dateFormat',
configuration.name('blame')('dateFormat').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.format',
configuration.name('blame')('format').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.heatmap.enabled',
configuration.name('blame')('heatmap')('enabled').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.heatmap.location',
configuration.name('blame')('heatmap')('location').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.lineHighlight.enabled',
configuration.name('blame')('highlight')('enabled').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.lineHighlight.locations',
configuration.name('blame')('highlight')('locations').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.separateLines',
configuration.name('blame')('separateLines').value
);
await configuration.migrateIfMissing('codeLens.locations', configuration.name('codeLens')('scopes').value);
await configuration.migrateIfMissing<{ customSymbols?: string[], language: string | undefined, locations: CodeLensScopes[] }[], CodeLensLanguageScope[]>(
'codeLens.perLanguageLocations', configuration.name('codeLens')('scopesByLanguage').value, {
migrationFn: v => {
const scopes = v.map(ls => {
return {
language: ls.language,
scopes: ls.locations,
symbolScopes: ls.customSymbols
};
});
return scopes;
}
});
await configuration.migrateIfMissing('codeLens.customLocationSymbols', configuration.name('codeLens')('symbolScopes').value);
await configuration.migrateIfMissing('annotations.line.trailing.dateFormat', configuration.name('currentLine')('dateFormat').value);
await configuration.migrateIfMissing('blame.line.enabled', configuration.name('currentLine')('enabled').value);
await configuration.migrateIfMissing('annotations.line.trailing.format', configuration.name('currentLine')('format').value);
await configuration.migrateIfMissing('annotations.file.gutter.hover.changes', configuration.name('hovers')('annotations')('changes').value);
await configuration.migrateIfMissing('annotations.file.gutter.hover.details', configuration.name('hovers')('annotations')('details').value);
await configuration.migrateIfMissing('annotations.file.gutter.hover.details', configuration.name('hovers')('annotations')('enabled').value);
await configuration.migrateIfMissing<
{ customSymbols?: string[]; language: string | undefined; locations: CodeLensScopes[] }[],
CodeLensLanguageScope[]
>('codeLens.perLanguageLocations', configuration.name('codeLens')('scopesByLanguage').value, {
migrationFn: v => {
const scopes = v.map(ls => {
return {
language: ls.language,
scopes: ls.locations,
symbolScopes: ls.customSymbols
};
});
return scopes;
}
});
await configuration.migrateIfMissing(
'codeLens.customLocationSymbols',
configuration.name('codeLens')('symbolScopes').value
);
await configuration.migrateIfMissing(
'annotations.line.trailing.dateFormat',
configuration.name('currentLine')('dateFormat').value
);
await configuration.migrateIfMissing(
'blame.line.enabled',
configuration.name('currentLine')('enabled').value
);
await configuration.migrateIfMissing(
'annotations.line.trailing.format',
configuration.name('currentLine')('format').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.hover.changes',
configuration.name('hovers')('annotations')('changes').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.hover.details',
configuration.name('hovers')('annotations')('details').value
);
await configuration.migrateIfMissing(
'annotations.file.gutter.hover.details',
configuration.name('hovers')('annotations')('enabled').value
);
await configuration.migrateIfMissing<boolean, 'line' | 'annotation'>(
'annotations.file.gutter.hover.wholeLine', configuration.name('hovers')('annotations')('over').value,
{ migrationFn: v => v ? 'line' : 'annotation' });
await configuration.migrateIfMissing('annotations.line.trailing.hover.changes', configuration.name('hovers')('currentLine')('changes').value);
await configuration.migrateIfMissing('annotations.line.trailing.hover.details', configuration.name('hovers')('currentLine')('details').value);
await configuration.migrateIfMissing('blame.line.enabled', configuration.name('hovers')('currentLine')('enabled').value);
'annotations.file.gutter.hover.wholeLine',
configuration.name('hovers')('annotations')('over').value,
{ migrationFn: v => (v ? 'line' : 'annotation') }
);
await configuration.migrateIfMissing(
'annotations.line.trailing.hover.changes',
configuration.name('hovers')('currentLine')('changes').value
);
await configuration.migrateIfMissing(
'annotations.line.trailing.hover.details',
configuration.name('hovers')('currentLine')('details').value
);
await configuration.migrateIfMissing(
'blame.line.enabled',
configuration.name('hovers')('currentLine')('enabled').value
);
await configuration.migrateIfMissing<boolean, 'line' | 'annotation'>(
'annotations.line.trailing.hover.wholeLine', configuration.name('hovers')('currentLine')('over').value,
{ migrationFn: v => v ? 'line' : 'annotation' });
await configuration.migrateIfMissing('gitExplorer.gravatars', configuration.name('explorers')('avatars').value);
await configuration.migrateIfMissing('gitExplorer.commitFileFormat', configuration.name('explorers')('commitFileFormat').value);
await configuration.migrateIfMissing('gitExplorer.commitFormat', configuration.name('explorers')('commitFormat').value);
await configuration.migrateIfMissing('gitExplorer.stashFileFormat', configuration.name('explorers')('stashFileFormat').value);
await configuration.migrateIfMissing('gitExplorer.stashFormat', configuration.name('explorers')('stashFormat').value);
await configuration.migrateIfMissing('gitExplorer.statusFileFormat', configuration.name('explorers')('statusFileFormat').value);
await configuration.migrateIfMissing('recentChanges.file.lineHighlight.locations', configuration.name('recentChanges')('highlight')('locations').value);
'annotations.line.trailing.hover.wholeLine',
configuration.name('hovers')('currentLine')('over').value,
{ migrationFn: v => (v ? 'line' : 'annotation') }
);
await configuration.migrateIfMissing(
'gitExplorer.gravatars',
configuration.name('explorers')('avatars').value
);
await configuration.migrateIfMissing(
'gitExplorer.commitFileFormat',
configuration.name('explorers')('commitFileFormat').value
);
await configuration.migrateIfMissing(
'gitExplorer.commitFormat',
configuration.name('explorers')('commitFormat').value
);
await configuration.migrateIfMissing(
'gitExplorer.stashFileFormat',
configuration.name('explorers')('stashFileFormat').value
);
await configuration.migrateIfMissing(
'gitExplorer.stashFormat',
configuration.name('explorers')('stashFormat').value
);
await configuration.migrateIfMissing(
'gitExplorer.statusFileFormat',
configuration.name('explorers')('statusFileFormat').value
);
await configuration.migrateIfMissing(
'recentChanges.file.lineHighlight.locations',
configuration.name('recentChanges')('highlight')('locations').value
);
}
if (Versions.compare(previous, Versions.from(8, 0, 2)) !== 1) {
const section = configuration.name('keymap').value;
await configuration.migrate<'standard' | 'chorded' | 'none', KeyMap>(section, section, {
fallbackValue: KeyMap.Alternate,
migrationFn: v => v === 'standard' ? KeyMap.Alternate : v as KeyMap
migrationFn: v => (v === 'standard' ? KeyMap.Alternate : (v as KeyMap))
});
}
if (Versions.compare(previous, Versions.from(8, 2, 4)) !== 1) {
await configuration.migrate<{
explorerContext: {
fileDiff: boolean,
history: boolean,
remote: boolean
},
editorContext: {
blame: boolean,
copy: boolean,
details: boolean,
fileDiff: boolean,
history: boolean,
lineDiff: boolean,
remote: boolean
await configuration.migrate<
{
explorerContext: {
fileDiff: boolean;
history: boolean;
remote: boolean;
};
editorContext: {
blame: boolean;
copy: boolean;
details: boolean;
fileDiff: boolean;
history: boolean;
lineDiff: boolean;
remote: boolean;
};
editorTitle: {
blame: boolean;
fileDiff: boolean;
history: boolean;
remote: boolean;
status: boolean;
};
editorTitleContext: {
blame: boolean;
fileDiff: boolean;
history: boolean;
remote: boolean;
};
},
editorTitle: {
blame: boolean,
fileDiff: boolean,
history: boolean,
remote: boolean,
status: boolean
},
editorTitleContext: {
blame: boolean,
fileDiff: boolean,
history: boolean,
remote: boolean
IMenuConfig
>('advanced.menus', configuration.name('menus').value, {
migrationFn: m => {
return {
editor: {
blame: !!m.editorContext.blame,
clipboard: !!m.editorContext.copy,
compare: !!m.editorContext.lineDiff,
details: !!m.editorContext.details,
history: !!m.editorContext.history,
remote: !!m.editorContext.remote
},
editorGroup: {
blame: !!m.editorTitle.blame,
compare: !!m.editorTitle.fileDiff,
history: !!m.editorTitle.history,
remote: !!m.editorTitle.remote
},
editorTab: {
compare: !!m.editorTitleContext.fileDiff,
history: !!m.editorTitleContext.history,
remote: !!m.editorTitleContext.remote
},
explorer: {
compare: !!m.explorerContext.fileDiff,
history: !!m.explorerContext.history,
remote: !!m.explorerContext.remote
}
} as IMenuConfig;
}
},
IMenuConfig>(
'advanced.menus', configuration.name('menus').value, {
migrationFn: m => {
return {
editor: {
blame: !!m.editorContext.blame,
clipboard: !!m.editorContext.copy,
compare: !!m.editorContext.lineDiff,
details: !!m.editorContext.details,
history: !!m.editorContext.history,
remote: !!m.editorContext.remote
},
editorGroup: {
blame: !!m.editorTitle.blame,
compare: !!m.editorTitle.fileDiff,
history: !!m.editorTitle.history,
remote: !!m.editorTitle.remote
},
editorTab: {
compare: !!m.editorTitleContext.fileDiff,
history: !!m.editorTitleContext.history,
remote: !!m.editorTitleContext.remote
},
explorer: {
compare: !!m.explorerContext.fileDiff,
history: !!m.explorerContext.history,
remote: !!m.explorerContext.remote
}
} as IMenuConfig;
}
});
});
}
}
catch (ex) {

+ 13
- 5
src/git/formatters/commitFormatter.ts View File

@ -21,7 +21,6 @@ export interface ICommitFormatOptions extends IFormatOptions {
}
export class CommitFormatter extends Formatter<GitCommit, ICommitFormatOptions> {
private get _ago() {
return this._item.fromNow();
}
@ -31,7 +30,8 @@ export class CommitFormatter extends Formatter
}
private get _agoOrDate() {
const dateStyle = this._options.dateStyle !== undefined ? this._options.dateStyle : Container.config.defaultDateStyle;
const dateStyle =
this._options.dateStyle !== undefined ? this._options.dateStyle : Container.config.defaultDateStyle;
return dateStyle === DateStyle.Absolute ? this._date : this._ago;
}
@ -92,8 +92,16 @@ export class CommitFormatter extends Formatter
static fromTemplate(template: string, commit: GitCommit, dateFormat: string | null): string;
static fromTemplate(template: string, commit: GitCommit, options?: ICommitFormatOptions): string;
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string;
static fromTemplate(template: string, commit: GitCommit, dateFormatOrOptions?: string | null | ICommitFormatOptions): string {
static fromTemplate(
template: string,
commit: GitCommit,
dateFormatOrOptions?: string | null | ICommitFormatOptions
): string;
static fromTemplate(
template: string,
commit: GitCommit,
dateFormatOrOptions?: string | null | ICommitFormatOptions
): string {
return super.fromTemplateCore(this, template, commit, dateFormatOrOptions);
}
}
}

+ 16
- 6
src/git/formatters/formatter.ts View File

@ -9,7 +9,6 @@ export interface IFormatOptions {
type Constructor<T = {}> = new (...args: any[]) => T;
export abstract class Formatter<TItem = any, TOptions extends IFormatOptions = IFormatOptions> {
protected _item!: TItem;
protected _options!: TOptions;
@ -88,16 +87,27 @@ export abstract class Formatter
private static _formatter: Formatter | undefined = undefined;
protected static fromTemplateCore<TFormatter extends Formatter<TItem, TOptions>, TItem, TOptions extends IFormatOptions>(formatter: TFormatter | Constructor<TFormatter>, template: string, item: TItem, dateFormatOrOptions?: string | null | TOptions): string {
protected static fromTemplateCore<
TFormatter extends Formatter<TItem, TOptions>,
TItem,
TOptions extends IFormatOptions
>(
formatter: TFormatter | Constructor<TFormatter>,
template: string,
item: TItem,
dateFormatOrOptions?: string | null | TOptions
): string {
if (formatter instanceof Formatter) return Strings.interpolate(template, formatter);
let options: TOptions | undefined = undefined;
if (dateFormatOrOptions == null || typeof dateFormatOrOptions === 'string') {
const tokenOptions = Strings.getTokensFromTemplate(template)
.reduce((map, token) => {
const tokenOptions = Strings.getTokensFromTemplate(template).reduce(
(map, token) => {
map[token.key] = token.options;
return map;
}, {} as { [token: string]: Strings.ITokenOptions | undefined });
},
{} as { [token: string]: Strings.ITokenOptions | undefined }
);
options = {
dateFormat: dateFormatOrOptions,
@ -117,4 +127,4 @@ export abstract class Formatter
return Strings.interpolate(template, this._formatter);
}
}
}

+ 12
- 5
src/git/formatters/statusFormatter.ts View File

@ -18,7 +18,6 @@ export interface IStatusFormatOptions extends IFormatOptions {
}
export class StatusFileFormatter extends Formatter<IGitStatusFile, IStatusFormatOptions> {
get directory() {
const directory = GitStatusFile.getFormattedDirectory(this._item, false, this._options.relativePath);
return this._padOrTruncate(directory, this._options.tokenOptions!.file);
@ -46,13 +45,21 @@ export class StatusFileFormatter extends Formatter
get working() {
const commit = (this._item as IGitStatusFileWithCommit).commit;
return (commit !== undefined && commit.isUncommitted) ? `${GlyphChars.Pencil} ${GlyphChars.Space}` : '';
return commit !== undefined && commit.isUncommitted ? `${GlyphChars.Pencil} ${GlyphChars.Space}` : '';
}
static fromTemplate(template: string, status: IGitStatusFile, dateFormat: string | null): string;
static fromTemplate(template: string, status: IGitStatusFile, options?: IStatusFormatOptions): string;
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string;
static fromTemplate(template: string, status: IGitStatusFile, dateFormatOrOptions?: string | null | IStatusFormatOptions): string {
static fromTemplate(
template: string,
status: IGitStatusFile,
dateFormatOrOptions?: string | null | IStatusFormatOptions
): string;
static fromTemplate(
template: string,
status: IGitStatusFile,
dateFormatOrOptions?: string | null | IStatusFormatOptions
): string {
return super.fromTemplateCore(this, template, status, dateFormatOrOptions);
}
}
}

+ 118
- 38
src/git/git.ts View File

@ -23,13 +23,13 @@ const sl = '%x2f'; // `%x${'/'.charCodeAt(0).toString(16)}`;
const logFormat = [
`${lb}${sl}f${rb}`,
`${lb}r${rb} %H`, // ref
`${lb}a${rb} %an`, // author
`${lb}e${rb} %ae`, // email
`${lb}d${rb} %at`, // date
`${lb}p${rb} %P`, // parents
`${lb}r${rb} %H`, // ref
`${lb}a${rb} %an`, // author
`${lb}e${rb} %ae`, // email
`${lb}d${rb} %at`, // date
`${lb}p${rb} %P`, // parents
`${lb}s${rb}`,
`%B`, // summary
`%B`, // summary
`${lb}${sl}s${rb}`,
`${lb}f${rb}`
].join('%n');
@ -38,11 +38,11 @@ const defaultLogParams = ['log', '--name-status', '-M', `--format=${logFormat}`]
const stashFormat = [
`${lb}${sl}f${rb}`,
`${lb}r${rb} %H`, // ref
`${lb}d${rb} %at`, // date
`${lb}l${rb} %gd`, // reflog-selector
`${lb}r${rb} %H`, // ref
`${lb}d${rb} %at`, // date
`${lb}l${rb} %gd`, // reflog-selector
`${lb}s${rb}`,
`%B`, // summary
`%B`, // summary
`${lb}${sl}s${rb}`,
`${lb}f${rb}`
].join('%n');
@ -62,10 +62,14 @@ const GitWarnings = {
foundButNotInRevision: /Path \'.*?\' exists on disk, but not in/i,
headNotABranch: /HEAD does not point to a branch/i,
noUpstream: /no upstream configured for branch \'(.*?)\'/i,
// tslint:disable-next-line:max-line-length
unknownRevision: /ambiguous argument \'.*?\': unknown revision or path not in the working tree|not stored as a remote-tracking branch/i
};
async function gitCommand(options: CommandOptions & { readonly correlationKey?: string }, ...args: any[]): Promise<string> {
async function gitCommand(
options: CommandOptions & { readonly correlationKey?: string },
...args: any[]
): Promise<string> {
try {
return await gitCommandCore(options, ...args);
}
@ -77,7 +81,10 @@ async function gitCommand(options: CommandOptions & { readonly correlationKey?:
// A map of running git commands -- avoids running duplicate overlaping commands
const pendingCommands: Map<string, Promise<string>> = new Map();
async function gitCommandCore(options: CommandOptions & { readonly correlationKey?: string }, ...args: any[]): Promise<string> {
async function gitCommandCore(
options: CommandOptions & { readonly correlationKey?: string },
...args: any[]
): Promise<string> {
const start = process.hrtime();
const { correlationKey, ...opts } = options;
@ -122,7 +129,8 @@ async function gitCommandCore(options: CommandOptions & { readonly correlationKe
pendingCommands.delete(command);
const duration = process.hrtime(start);
const completedIn = `${exception === undefined ? 'Completed' : 'FAILED'} in ${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms`;
const completedIn = `${exception === undefined ? 'Completed' : 'FAILED'} in ${duration[0] * 1000 +
Math.floor(duration[1] / 1000000)} ms`;
Logger.log(`${exception === undefined ? 'Completed' : 'FAILED'}${command} ${completedIn}`);
Logger.logGitCommand(`${gitCommand} ${completedIn}`, runOpts.cwd!, exception);
@ -149,7 +157,6 @@ function gitCommandDefaultErrorHandler(ex: Error, options: CommandOptions, ...ar
}
export class Git {
static shaRegex = /^[0-9a-f]{40}(\^[0-9]*?)??( -)?$/;
static shaStrictRegex = /^[0-9a-f]{40}$/;
static stagedUncommittedRegex = /^[0]{40}(\^[0-9]*?)??:$/;
@ -162,9 +169,7 @@ export class Git {
}
static getEncoding(encoding: string | undefined) {
return (encoding !== undefined && iconv.encodingExists(encoding))
? encoding
: 'utf8';
return encoding !== undefined && iconv.encodingExists(encoding) ? encoding : 'utf8';
}
static async getGitInfo(gitPath?: string): Promise<IGit> {
@ -173,7 +178,10 @@ export class Git {
git = await findGitPath(gitPath);
const duration = process.hrtime(start);
Logger.log(`Git found: ${git.version} @ ${git.path === 'git' ? 'PATH' : git.path} in ${(duration[0] * 1000) + Math.floor(duration[1] / 1000000)} ms`);
Logger.log(
`Git found: ${git.version} @ ${git.path === 'git' ? 'PATH' : git.path} in ${duration[0] * 1000 +
Math.floor(duration[1] / 1000000)} ms`
);
return git;
}
@ -186,12 +194,17 @@ export class Git {
ref = '';
}
const suffix = Strings.truncate(Strings.sanitizeForFileSystem(Git.isSha(ref) ? Git.shortenSha(ref)! : ref), 50, '');
const suffix = Strings.truncate(
Strings.sanitizeForFileSystem(Git.isSha(ref) ? Git.shortenSha(ref)! : ref),
50,
''
);
const ext = path.extname(fileName);
const tmp = await import('tmp');
return new Promise<string>((resolve, reject) => {
tmp.file({ prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext },
tmp.file(
{ prefix: `${path.basename(fileName, ext)}-${suffix}__`, postfix: ext },
(err, destination, fd, cleanupCallback) => {
if (err) {
reject(err);
@ -210,7 +223,8 @@ export class Git {
resolve(destination);
});
});
});
}
);
});
}
@ -230,7 +244,10 @@ export class Git {
return sha === undefined ? false : Git.uncommittedRegex.test(sha);
}
static shortenSha(sha: string, strings: { stagedUncommitted?: string, uncommitted?: string, working?: string } = {}) {
static shortenSha(
sha: string,
strings: { stagedUncommitted?: string; uncommitted?: string; working?: string } = {}
) {
strings = { stagedUncommitted: 'index', uncommitted: 'working', working: '', ...strings };
if (sha === '') return strings.working;
@ -261,17 +278,22 @@ export class Git {
fileName = Strings.normalizePath(extract ? path.basename(fileName) : fileName);
}
return [ fileName, repoPath ];
return [fileName, repoPath];
}
static validateVersion(major: number, minor: number): boolean {
const [gitMajor, gitMinor] = git.version.split('.');
return (parseInt(gitMajor, 10) >= major && parseInt(gitMinor, 10) >= minor);
return parseInt(gitMajor, 10) >= major && parseInt(gitMinor, 10) >= minor;
}
// Git commands
static async blame(repoPath: string | undefined, fileName: string, sha?: string, options: { args?: string[] | null, ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
static async blame(
repoPath: string | undefined,
fileName: string,
sha?: string,
options: { args?: string[] | null; ignoreWhitespace?: boolean; startLine?: number; endLine?: number } = {}
) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultBlameParams];
@ -303,7 +325,18 @@ export class Git {
return gitCommand({ cwd: root, stdin: stdin }, ...params, '--', file);
}
static async blame_contents(repoPath: string | undefined, fileName: string, contents: string, options: { args?: string[] | null, correlationKey?: string, ignoreWhitespace?: boolean, startLine?: number, endLine?: number } = {}) {
static async blame_contents(
repoPath: string | undefined,
fileName: string,
contents: string,
options: {
args?: string[] | null;
correlationKey?: string;
ignoreWhitespace?: boolean;
startLine?: number;
endLine?: number;
} = {}
) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultBlameParams];
@ -321,7 +354,12 @@ export class Git {
// Pipe the blame contents to stdin
params.push('--contents', '-');
return gitCommand({ cwd: root, stdin: contents, correlationKey: options.correlationKey }, ...params, '--', file);
return gitCommand(
{ cwd: root, stdin: contents, correlationKey: options.correlationKey },
...params,
'--',
file
);
}
static branch(repoPath: string, options: { all: boolean } = { all: false }) {
@ -413,7 +451,7 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params);
}
static log(repoPath: string, options: { maxCount?: number, ref?: string, reverse?: boolean }) {
static log(repoPath: string, options: { maxCount?: number; ref?: string; reverse?: boolean }) {
const params = [...defaultLogParams, '--full-history', '-m'];
if (options.maxCount && !options.reverse) {
params.push(`-n${options.maxCount}`);
@ -429,7 +467,18 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params, '--');
}
static log_file(repoPath: string, fileName: string, options: { maxCount?: number, ref?: string, renames?: boolean, reverse?: boolean, startLine?: number, endLine?: number } = { renames: true, reverse: false }) {
static log_file(
repoPath: string,
fileName: string,
options: {
maxCount?: number;
ref?: string;
renames?: boolean;
reverse?: boolean;
startLine?: number;
endLine?: number;
} = { renames: true, reverse: false }
) {
const [file, root] = Git.splitPath(fileName, repoPath);
const params = [...defaultLogParams];
@ -469,7 +518,16 @@ export class Git {
static async log_resolve(repoPath: string, fileName: string, ref: string) {
try {
const data = await gitCommandCore({ cwd: repoPath }, 'log', '-M', '-n1', '--format=%H', ref, '--', fileName);
const data = await gitCommandCore(
{ cwd: repoPath },
'log',
'-M',
'-n1',
'--format=%H',
ref,
'--',
fileName
);
return data.trim();
}
catch {
@ -576,7 +634,12 @@ export class Git {
}
}
static async show(repoPath: string | undefined, fileName: string, ref: string, options: { encoding?: string } = {}): Promise<string | undefined> {
static async show(
repoPath: string | undefined,
fileName: string,
ref: string,
options: { encoding?: string } = {}
): Promise<string | undefined> {
const [file, root] = Git.splitPath(fileName, repoPath);
if (Git.isStagedUncommitted(ref)) {
@ -585,9 +648,7 @@ export class Git {
if (Git.isUncommitted(ref)) throw new Error(`sha=${ref} is uncommitted`);
const opts = { cwd: root, encoding: options.encoding || 'utf8' } as CommandOptions;
const args = ref.endsWith(':')
? `${ref}./${file}`
: `${ref}:./${file}`;
const args = ref.endsWith(':') ? `${ref}./${file}` : `${ref}:./${file}`;
try {
const data = await gitCommandCore(opts, 'show', args, '--');
@ -599,9 +660,13 @@ export class Git {
return Git.show(repoPath, fileName, 'HEAD:', options);
}
if (GitErrors.badRevision.test(msg) ||
if (
GitErrors.badRevision.test(msg) ||
GitWarnings.notFound.test(msg) ||
GitWarnings.foundButNotInRevision.test(msg)) return undefined;
GitWarnings.foundButNotInRevision.test(msg)
) {
return undefined;
}
return gitCommandDefaultErrorHandler(ex, opts, args);
}
@ -640,14 +705,29 @@ export class Git {
static status(repoPath: string, porcelainVersion: number = 1): Promise<string> {
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
return gitCommand({ cwd: repoPath, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } }, '-c', 'color.status=false', 'status', porcelain, '--branch', '-u');
return gitCommand(
{ cwd: repoPath, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } },
'-c',
'color.status=false',
'status',
porcelain,
'--branch',
'-u'
);
}
static status_file(repoPath: string, fileName: string, porcelainVersion: number = 1): Promise<string> {
const [file, root] = Git.splitPath(fileName, repoPath);
const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain';
return gitCommand({ cwd: root, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } }, '-c', 'color.status=false', 'status', porcelain, file);
return gitCommand(
{ cwd: root, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } },
'-c',
'color.status=false',
'status',
porcelain,
file
);
}
static tag(repoPath: string) {

+ 7
- 4
src/git/gitLocator.ts View File

@ -69,13 +69,16 @@ export async function findGitPath(path?: string): Promise {
catch (ex) {
try {
switch (process.platform) {
case 'darwin': return await findGitDarwin();
case 'win32': return await findGitWin32();
default: return Promise.reject('Unable to find git');
case 'darwin':
return await findGitDarwin();
case 'win32':
return await findGitWin32();
default:
return Promise.reject('Unable to find git');
}
}
catch (ex) {
return Promise.reject(new Error('Unable to find git'));
}
}
}
}

+ 38
- 23
src/git/gitUri.ts View File

@ -23,20 +23,19 @@ interface UriComponents {
}
interface UriEx {
new(): Uri;
new(scheme: string, authority: string, path: string, query: string, fragment: string): Uri;
new (): Uri;
new (scheme: string, authority: string, path: string, query: string, fragment: string): Uri;
// Use this ctor, because vscode doesn't validate it
new(components: UriComponents): Uri;
new (components: UriComponents): Uri;
}
export class GitUri extends ((Uri as any) as UriEx) {
repoPath?: string;
sha?: string;
versionedPath?: string;
constructor(uri?: Uri)
constructor(uri?: Uri);
constructor(uri: Uri, commit: IGitCommitInfo);
constructor(uri: Uri, repoPath: string | undefined);
constructor(uri?: Uri, commitOrRepoPath?: IGitCommitInfo | string) {
@ -49,7 +48,10 @@ export class GitUri extends ((Uri as any) as UriEx) {
if (uri.scheme === DocumentSchemes.GitLensGit) {
const data: IUriRevisionData = JSON.parse(uri.query);
const [authority, fsPath] = GitUri.ensureValidUNCPath(uri.authority, path.resolve(data.repoPath, data.fileName));
const [authority, fsPath] = GitUri.ensureValidUNCPath(
uri.authority,
path.resolve(data.repoPath, data.fileName)
);
super({ scheme: uri.scheme, authority: authority, path: fsPath, query: uri.query, fragment: uri.fragment });
this.repoPath = data.repoPath;
@ -74,7 +76,10 @@ export class GitUri extends ((Uri as any) as UriEx) {
return;
}
const [authority, fsPath] = GitUri.ensureValidUNCPath(uri.authority, path.resolve(commitOrRepoPath.repoPath, commitOrRepoPath.fileName || uri.fsPath));
const [authority, fsPath] = GitUri.ensureValidUNCPath(
uri.authority,
path.resolve(commitOrRepoPath.repoPath, commitOrRepoPath.fileName || uri.fsPath)
);
super({ scheme: uri.scheme, authority: authority, path: fsPath, query: uri.query, fragment: uri.fragment });
this.repoPath = commitOrRepoPath.repoPath;
@ -88,7 +93,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
return this.sha && GitService.shortenSha(this.sha);
}
fileUri(options: { noSha?: boolean, useVersionedPath?: boolean } = {}) {
fileUri(options: { noSha?: boolean; useVersionedPath?: boolean } = {}) {
if (options.useVersionedPath && this.versionedPath !== undefined) return Uri.file(this.versionedPath);
return Uri.file(!options.noSha && this.sha ? this.path : this.fsPath);
@ -112,7 +117,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
}
directory = Strings.normalizePath(directory);
return (!directory || directory === '.')
return !directory || directory === '.'
? path.basename(this.fsPath)
: `${path.basename(this.fsPath)}${separator}${directory}`;
}
@ -133,7 +138,8 @@ export class GitUri extends ((Uri as any) as UriEx) {
if (index === -1) {
authority = fsPath.substring(2);
fsPath = '\';
} else {
}
else {
authority = fsPath.substring(2, index);
fsPath = fsPath.substring(index) || '\';
}
@ -153,9 +159,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
static fromFileStatus(status: IGitStatusFile, repoPath: string, sha?: string, original: boolean = false): GitUri {
const uri = Uri.file(path.resolve(repoPath, (original && status.originalFileName) || status.fileName));
return sha === undefined
? new GitUri(uri, repoPath)
: new GitUri(uri, { repoPath: repoPath, sha: sha });
return sha === undefined ? new GitUri(uri, repoPath) : new GitUri(uri, { repoPath: repoPath, sha: sha });
}
static fromRepoPath(repoPath: string, ref?: string) {
@ -177,7 +181,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
// If this is a git uri, find its repoPath
if (uri.scheme === DocumentSchemes.Git) {
const data: { path: string, ref: string } = JSON.parse(uri.query);
const data: { path: string; ref: string } = JSON.parse(uri.query);
const repoPath = await Container.git.getRepoPath(data.path);
@ -216,10 +220,14 @@ export class GitUri extends ((Uri as any) as UriEx) {
directory = path.relative(relativeTo, directory);
}
directory = Strings.normalizePath(directory);
return (!directory || directory === '.') ? '' : directory;
return !directory || directory === '.' ? '' : directory;
}
static getFormattedPath(fileNameOrUri: string | Uri, separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
static getFormattedPath(
fileNameOrUri: string | Uri,
separator: string = Strings.pad(GlyphChars.Dot, 2, 2),
relativeTo?: string
): string {
let fileName: string;
if (fileNameOrUri instanceof Uri) {
if (fileNameOrUri instanceof GitUri) return fileNameOrUri.getFormattedPath(separator, relativeTo);
@ -231,9 +239,7 @@ export class GitUri extends ((Uri as any) as UriEx) {
}
const directory = GitUri.getDirectory(fileName, relativeTo);
return !directory
? path.basename(fileName)
: `${path.basename(fileName)}${separator}${directory}`;
return !directory ? path.basename(fileName) : `${path.basename(fileName)}${separator}${directory}`;
}
static getRelativePath(fileNameOrUri: string | Uri, relativeTo?: string, repoPath?: string): string {
@ -258,13 +264,19 @@ export class GitUri extends ((Uri as any) as UriEx) {
static toKey(uri: Uri): string;
static toKey(fileNameOrUri: string | Uri): string;
static toKey(fileNameOrUri: string | Uri): string {
return Strings.normalizePath(typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath).toLowerCase();
return Strings.normalizePath(
typeof fileNameOrUri === 'string' ? fileNameOrUri : fileNameOrUri.fsPath
).toLowerCase();
}
static toRevisionUri(uri: GitUri): Uri;
static toRevisionUri(sha: string, fileName: string, repoPath: string): Uri;
static toRevisionUri(sha: string, status: IGitStatusFile, repoPath: string): Uri;
static toRevisionUri(uriOrSha: string | GitUri, fileNameOrStatus?: string | IGitStatusFile, repoPath?: string): Uri {
static toRevisionUri(
uriOrSha: string | GitUri,
fileNameOrStatus?: string | IGitStatusFile,
repoPath?: string
): Uri {
let fileName: string;
let sha: string | undefined;
let shortSha: string | undefined;
@ -294,9 +306,12 @@ export class GitUri extends ((Uri as any) as UriEx) {
};
const parsed = path.parse(fileName);
return Uri.parse(`${DocumentSchemes.GitLensGit}:${path.join(parsed.dir, parsed.name)}:${shortSha}${parsed.ext}?${JSON.stringify(data)}`);
return Uri.parse(
`${DocumentSchemes.GitLensGit}:${path.join(parsed.dir, parsed.name)}:${shortSha}${
parsed.ext
}?${JSON.stringify(data)}`
);
}
}
interface IUriRevisionData {

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

@ -23,4 +23,4 @@ export interface GitBlameCommitLines {
readonly author: GitAuthor;
readonly commit: GitBlameCommit;
readonly lines: GitCommitLine[];
}
}

+ 10
- 4
src/git/models/blameCommit.ts View File

@ -2,7 +2,6 @@
import { GitCommit, GitCommitLine, GitCommitType } from './commit';
export class GitBlameCommit extends GitCommit {
constructor(
repoPath: string,
sha: string,
@ -37,7 +36,14 @@ export class GitBlameCommit extends GitCommit {
return `${this.sha}^`;
}
with(changes: { sha?: string, fileName?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, lines?: GitCommitLine[] | null }): GitBlameCommit {
with(changes: {
sha?: string;
fileName?: string;
originalFileName?: string | null;
previousFileName?: string | null;
previousSha?: string | null;
lines?: GitCommitLine[] | null;
}): GitBlameCommit {
return new GitBlameCommit(
this.repoPath,
changes.sha || this.sha,
@ -49,7 +55,7 @@ export class GitBlameCommit extends GitCommit {
this.getChangedValue(changes.originalFileName, this.originalFileName),
this.getChangedValue(changes.previousSha, this.previousSha),
this.getChangedValue(changes.previousFileName, this.previousFileName),
this.getChangedValue(changes.lines, (changes.sha || changes.fileName) ? [] : this.lines) || []
this.getChangedValue(changes.lines, changes.sha || changes.fileName ? [] : this.lines) || []
);
}
}
}

+ 11
- 12
src/git/models/branch.ts View File

@ -3,7 +3,6 @@ import { GlyphChars } from '../../constants';
'use strict';
export class GitBranch {
readonly name: string;
readonly remote: boolean;
readonly tracking?: string;
@ -30,7 +29,7 @@ export class GitBranch {
}
this.name = branch;
this.tracking = (tracking === '' || tracking == null) ? undefined : tracking;
this.tracking = tracking === '' || tracking == null ? undefined : tracking;
this.state = {
ahead: ahead,
behind: behind
@ -42,9 +41,7 @@ export class GitBranch {
if (this._basename === undefined) {
const name = this.getName();
const index = name.lastIndexOf('/');
this._basename = index !== -1
? name.substring(index + 1)
: name;
this._basename = index !== -1 ? name.substring(index + 1) : name;
}
return this._basename;
@ -53,9 +50,7 @@ export class GitBranch {
private _name: string | undefined;
getName(): string {
if (this._name === undefined) {
this._name = this.remote
? this.name.substring(this.name.indexOf('/') + 1)
: this.name;
this._name = this.remote ? this.name.substring(this.name.indexOf('/') + 1) : this.name;
}
return this._name;
@ -68,7 +63,7 @@ export class GitBranch {
return undefined;
}
getTrackingStatus(options: { empty?: string, expand?: boolean, prefix?: string, separator?: string } = {}): string {
getTrackingStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string {
options = { empty: '', prefix: '', separator: ' ', ...options };
if (this.tracking === undefined || (this.state.behind === 0 && this.state.ahead === 0)) return options.empty!;
@ -78,12 +73,16 @@ export class GitBranch {
status += `${this.state.behind} ${this.state.behind === 1 ? 'commit' : 'commits'} behind`;
}
if (this.state.ahead) {
status += `${status === '' ? '' : options.separator}${this.state.ahead} ${this.state.ahead === 1 ? 'commit' : 'commits'} ahead`;
status += `${status === '' ? '' : options.separator}${this.state.ahead} ${
this.state.ahead === 1 ? 'commit' : 'commits'
} ahead`;
}
return `${options.prefix}${status}`;
}
return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${GlyphChars.ArrowUp}`;
return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${
GlyphChars.ArrowUp
}`;
}
isValid(): boolean {
@ -99,4 +98,4 @@ export class GitBranch {
// Deals with detached HEAD states
return name.match(/\s/) === null;
}
}
}

+ 28
- 10
src/git/models/commit.ts View File

@ -45,7 +45,6 @@ export const CommitFormatting = {
};
export abstract class GitCommit {
readonly type: GitCommitType;
readonly originalFileName: string | undefined;
previousFileName: string | undefined;
@ -97,7 +96,11 @@ export abstract class GitCommit {
}
get isFile() {
return this.type === GitCommitType.Blame || this.type === GitCommitType.File || this.type === GitCommitType.StashFile;
return (
this.type === GitCommitType.Blame ||
this.type === GitCommitType.File ||
this.type === GitCommitType.StashFile
);
}
get isStash() {
@ -140,7 +143,9 @@ export abstract class GitCommit {
}
get previousUri(): Uri {
return this.previousFileName ? Uri.file(path.resolve(this.repoPath, (this.previousFileName || this.originalFileName)!)) : this.uri;
return this.previousFileName
? Uri.file(path.resolve(this.repoPath, (this.previousFileName || this.originalFileName)!))
: this.uri;
}
get uri(): Uri {
@ -172,14 +177,16 @@ export abstract class GitCommit {
}
getGravatarUri(fallback: GravatarDefaultStyle, size: number = 16): Uri {
const key = this.email
? `${this.email.trim().toLowerCase()}:${size}`
: '';
const key = this.email ? `${this.email.trim().toLowerCase()}:${size}` : '';
let gravatar = gravatarCache.get(key);
if (gravatar !== undefined) return gravatar;
gravatar = Uri.parse(`https://www.gravatar.com/avatar/${this.email ? Strings.md5(this.email, 'hex') : '00000000000000000000000000000000'}.jpg?s=${size}&d=${fallback}`);
gravatar = Uri.parse(
`https://www.gravatar.com/avatar/${
this.email ? Strings.md5(this.email, 'hex') : '00000000000000000000000000000000'
}.jpg?s=${size}&d=${fallback}`
);
// HACK: Monkey patch Uri.toString to avoid the unwanted query string encoding
const originalToStringFn = gravatar.toString;
@ -202,17 +209,28 @@ export abstract class GitCommit {
async resolvePreviousFileSha(): Promise<void> {
if (this._resolvedPreviousFileSha !== undefined) return;
this._resolvedPreviousFileSha = await Container.git.resolveReference(this.repoPath, this.previousFileSha, this.fileName ? this.previousUri : undefined);
this._resolvedPreviousFileSha = await Container.git.resolveReference(
this.repoPath,
this.previousFileSha,
this.fileName ? this.previousUri : undefined
);
}
toGitUri(previous: boolean = false): GitUri {
return GitUri.fromCommit(this, previous);
}
abstract with(changes: { type?: GitCommitType, sha?: string, fileName?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null }): GitCommit;
abstract with(changes: {
type?: GitCommitType;
sha?: string;
fileName?: string;
originalFileName?: string | null;
previousFileName?: string | null;
previousSha?: string | null;
}): GitCommit;
protected getChangedValue<T>(change: T | null | undefined, original: T | undefined): T | undefined {
if (change === undefined) return original;
return change !== null ? change : undefined;
}
}
}

+ 4
- 5
src/git/models/diff.ts View File

@ -11,17 +11,16 @@ export interface GitDiffChunkLine extends GitDiffLine {
}
export class GitDiffChunk {
private _chunk: string | undefined;
private _lines: GitDiffChunkLine[] | undefined;
constructor(
chunk: string,
public currentPosition: { start: number, end: number },
public previousPosition: { start: number, end: number }
public currentPosition: { start: number; end: number },
public previousPosition: { start: number; end: number }
) {
this._chunk = chunk;
}
}
get lines(): GitDiffChunkLine[] {
if (this._lines === undefined) {
@ -43,4 +42,4 @@ export interface GitDiffShortStat {
readonly files: number;
readonly insertions: number;
readonly deletions: number;
}
}

+ 2
- 2
src/git/models/log.ts View File

@ -14,5 +14,5 @@ export interface GitLog {
readonly range: Range;
readonly truncated: boolean;
query: (maxCount: number | undefined) => Promise<GitLog | undefined>;
}
query(maxCount: number | undefined): Promise<GitLog | undefined>;
}

+ 17
- 9
src/git/models/logCommit.ts View File

@ -7,7 +7,6 @@ import { GitStatusFileStatus, IGitStatusFile } from './status';
import * as path from 'path';
export class GitLogCommit extends GitCommit {
nextSha?: string;
nextFileName?: string;
@ -57,9 +56,7 @@ export class GitLogCommit extends GitCommit {
get previousFileSha(): string {
if (this._resolvedPreviousFileSha !== undefined) return this._resolvedPreviousFileSha;
return (this.isFile && this.previousSha)
? this.previousSha
: `${this.sha}^`;
return this.isFile && this.previousSha ? this.previousSha : `${this.sha}^`;
}
getDiffStatus(): string {
@ -99,9 +96,7 @@ export class GitLogCommit extends GitCommit {
}
// If this isn't a single-file commit, we can't trust the previousSha
const previousSha = this.isFile
? this.previousSha
: `${this.sha}^`;
const previousSha = this.isFile ? this.previousSha : `${this.sha}^`;
return this.with({
type: this.isStash ? GitCommitType.StashFile : GitCommitType.File,
@ -114,7 +109,20 @@ export class GitLogCommit extends GitCommit {
});
}
with(changes: { type?: GitCommitType, sha?: string | null, fileName?: string, author?: string, email?: string, date?: Date, message?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, status?: GitStatusFileStatus, fileStatuses?: IGitStatusFile[] | null }): GitLogCommit {
with(changes: {
type?: GitCommitType;
sha?: string | null;
fileName?: string;
author?: string;
email?: string;
date?: Date;
message?: string;
originalFileName?: string | null;
previousFileName?: string | null;
previousSha?: string | null;
status?: GitStatusFileStatus;
fileStatuses?: IGitStatusFile[] | null;
}): GitLogCommit {
return new GitLogCommit(
changes.type || this.type,
this.repoPath,
@ -132,4 +140,4 @@ export class GitLogCommit extends GitCommit {
undefined
);
}
}
}

+ 3
- 4
src/git/models/remote.ts View File

@ -7,7 +7,6 @@ export enum GitRemoteType {
}
export class GitRemote {
constructor(
public readonly repoPath: string,
public readonly name: string,
@ -15,6 +14,6 @@ export class GitRemote {
public readonly domain: string,
public readonly path: string,
public readonly provider: RemoteProvider | undefined,
public readonly types: { type: GitRemoteType, url: string }[]
) { }
}
public readonly types: { type: GitRemoteType; url: string }[]
) {}
}

+ 36
- 15
src/git/models/repository.ts View File

@ -1,6 +1,15 @@
'use strict';
import { Functions } from '../../system';
import { ConfigurationChangeEvent, Disposable, Event, EventEmitter, RelativePattern, Uri, workspace, WorkspaceFolder } from 'vscode';
import {
ConfigurationChangeEvent,
Disposable,
Event,
EventEmitter,
RelativePattern,
Uri,
workspace,
WorkspaceFolder
} from 'vscode';
import { configuration, IRemotesConfig } from '../../configuration';
import { Container } from '../../container';
import { GitBranch, GitDiffShortStat, GitRemote, GitStash, GitStatus, GitTag } from '../git';
@ -19,12 +28,11 @@ export enum RepositoryChange {
}
export class RepositoryChangeEvent {
readonly changes: RepositoryChange[] = [];
constructor(
public readonly repository?: Repository
) { }
) {}
changed(change: RepositoryChange, solely: boolean = false) {
if (solely) return this.changes.length === 1 && this.changes[0] === change;
@ -52,7 +60,6 @@ export enum RepositoryStorage {
}
export class Repository extends Disposable {
private _onDidChange = new EventEmitter<RepositoryChangeEvent>();
get onDidChange(): Event<RepositoryChangeEvent> {
return this._onDidChange.event;
@ -75,7 +82,7 @@ export class Repository extends Disposable {
private _fireFileSystemChangeDebounced: ((e: RepositoryFileSystemChangeEvent) => void) | undefined = undefined;
private _fsWatchCounter = 0;
private _fsWatcherDisposable: Disposable | undefined;
private _pendingChanges: { repo?: RepositoryChangeEvent, fs?: RepositoryFileSystemChangeEvent } = { };
private _pendingChanges: { repo?: RepositoryChangeEvent; fs?: RepositoryFileSystemChangeEvent } = {};
private _providerMap: RemoteProviderMap | undefined;
private _remotes: Promise<GitRemote[]> | undefined;
private _suspended: boolean;
@ -95,9 +102,7 @@ export class Repository extends Disposable {
}
else {
const relativePath = _path.relative(folder.uri.fsPath, path);
this.formattedName = relativePath
? `${folder.name} (${relativePath})`
: folder.name;
this.formattedName = relativePath ? `${folder.name} (${relativePath})` : folder.name;
}
this.index = folder.index;
this.name = folder.name;
@ -108,7 +113,21 @@ export class Repository extends Disposable {
this._closed = closed;
// TODO: createFileSystemWatcher doesn't work unless the folder is part of the workspaceFolders
const watcher = workspace.createFileSystemWatcher(new RelativePattern(folder, '{**/.git/config,**/.git/index,**/.git/HEAD,**/.git/refs/stash,**/.git/refs/heads/**,**/.git/refs/remotes/**,**/.git/refs/tags/**,**/.gitignore}'));
const watcher = workspace.createFileSystemWatcher(
new RelativePattern(
folder,
'{\
**/.git/config,\
**/.git/index,\
**/.git/HEAD,\
**/.git/refs/stash,\
**/.git/refs/heads/**,\
**/.git/refs/remotes/**,\
**/.git/refs/tags/**,\
**/.gitignore\
}'
)
);
this._disposable = Disposable.from(
watcher,
watcher.onDidChange(this.onRepositoryChanged, this),
@ -137,7 +156,9 @@ export class Repository extends Disposable {
const section = configuration.name('remotes').value;
if (initializing || configuration.changed(e, section, this.folder.uri)) {
this._providerMap = RemoteProviderFactory.createMap(configuration.get<IRemotesConfig[] | null | undefined>(section, this.folder.uri));
this._providerMap = RemoteProviderFactory.createMap(
configuration.get<IRemotesConfig[] | null | undefined>(section, this.folder.uri)
);
if (!initializing) {
this._remotes = undefined;
@ -201,9 +222,7 @@ export class Repository extends Disposable {
containsUri(uri: Uri) {
if (uri instanceof GitUri) {
uri = uri.repoPath !== undefined
? Uri.file(uri.repoPath)
: uri.fileUri();
uri = uri.repoPath !== undefined ? Uri.file(uri.repoPath) : uri.fileUri();
}
return this.folder === workspace.getWorkspaceFolder(uri);
@ -227,7 +246,10 @@ export class Repository extends Disposable {
getRemotes(): Promise<GitRemote[]> {
if (this._remotes === undefined) {
if (this._providerMap === undefined) {
const remotesCfg = configuration.get<IRemotesConfig[] | null | undefined>(configuration.name('remotes').value, this.folder.uri);
const remotesCfg = configuration.get<IRemotesConfig[] | null | undefined>(
configuration.name('remotes').value,
this.folder.uri
);
this._providerMap = RemoteProviderFactory.createMap(remotesCfg);
}
@ -351,5 +373,4 @@ export class Repository extends Disposable {
this._onDidChangeFileSystem.fire(e);
}
}

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

@ -4,4 +4,4 @@ import { GitStashCommit } from './stashCommit';
export interface GitStash {
readonly repoPath: string;
readonly commits: Map<string, GitStashCommit>;
}
}

+ 13
- 3
src/git/models/stashCommit.ts View File

@ -4,7 +4,6 @@ import { GitLogCommit } from './logCommit';
import { GitStatusFileStatus, IGitStatusFile } from './status';
export class GitStashCommit extends GitLogCommit {
constructor(
type: GitCommitType,
public readonly stashName: string,
@ -40,7 +39,18 @@ export class GitStashCommit extends GitLogCommit {
return this.stashName;
}
with(changes: { type?: GitCommitType, sha?: string | null, fileName?: string, date?: Date, message?: string, originalFileName?: string | null, previousFileName?: string | null, previousSha?: string | null, status?: GitStatusFileStatus, fileStatuses?: IGitStatusFile[] | null }): GitLogCommit {
with(changes: {
type?: GitCommitType;
sha?: string | null;
fileName?: string;
date?: Date;
message?: string;
originalFileName?: string | null;
previousFileName?: string | null;
previousSha?: string | null;
status?: GitStatusFileStatus;
fileStatuses?: IGitStatusFile[] | null;
}): GitLogCommit {
return new GitStashCommit(
changes.type || this.type,
this.stashName,
@ -56,4 +66,4 @@ export class GitStashCommit extends GitLogCommit {
this.getChangedValue(changes.previousFileName, this.previousFileName)
);
}
}
}

+ 40
- 20
src/git/models/status.ts View File

@ -12,7 +12,6 @@ export interface GitStatusUpstreamState {
}
export class GitStatus {
constructor(
public readonly repoPath: string,
public readonly branch: string,
@ -20,15 +19,15 @@ export class GitStatus {
public readonly files: GitStatusFile[],
public readonly state: GitStatusUpstreamState,
public readonly upstream?: string
) { }
) {}
private _diff?: {
added: number,
deleted: number,
changed: number
added: number;
deleted: number;
changed: number;
};
getDiffStatus(options: { empty?: string, expand?: boolean, prefix?: string, separator?: string } = {}): string {
getDiffStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string {
options = { empty: '', prefix: '', separator: ' ', ...options };
if (this.files.length === 0) return options.empty!;
@ -53,7 +52,6 @@ export class GitStatus {
break;
}
}
}
if (options.expand) {
@ -62,18 +60,24 @@ export class GitStatus {
status += `${this._diff.added} ${this._diff.added === 1 ? 'file' : 'files'} added`;
}
if (this._diff.changed) {
status += `${status === '' ? '' : options.separator}${this._diff.changed} ${this._diff.changed === 1 ? 'file' : 'files'} changed`;
status += `${status === '' ? '' : options.separator}${this._diff.changed} ${
this._diff.changed === 1 ? 'file' : 'files'
} changed`;
}
if (this._diff.deleted) {
status += `${status === '' ? '' : options.separator}${this._diff.deleted} ${this._diff.deleted === 1 ? 'file' : 'files'} deleted`;
status += `${status === '' ? '' : options.separator}${this._diff.deleted} ${
this._diff.deleted === 1 ? 'file' : 'files'
} deleted`;
}
return `${options.prefix}${status}`;
}
return `${options.prefix}+${this._diff.added}${options.separator}~${this._diff.changed}${options.separator}-${this._diff.deleted}`;
return `${options.prefix}+${this._diff.added}${options.separator}~${this._diff.changed}${options.separator}-${
this._diff.deleted
}`;
}
getUpstreamStatus(options: { empty?: string, expand?: boolean, prefix?: string, separator?: string } = {}): string {
getUpstreamStatus(options: { empty?: string; expand?: boolean; prefix?: string; separator?: string } = {}): string {
options = { empty: '', prefix: '', separator: ' ', ...options };
if (this.upstream === undefined || (this.state.behind === 0 && this.state.ahead === 0)) return options.empty!;
@ -83,12 +87,16 @@ export class GitStatus {
status += `${this.state.behind} ${this.state.behind === 1 ? 'commit' : 'commits'} behind`;
}
if (this.state.ahead) {
status += `${status === '' ? '' : options.separator}${this.state.ahead} ${this.state.ahead === 1 ? 'commit' : 'commits'} ahead`;
status += `${status === '' ? '' : options.separator}${this.state.ahead} ${
this.state.ahead === 1 ? 'commit' : 'commits'
} ahead`;
}
return `${options.prefix}${status}`;
}
return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${GlyphChars.ArrowUp}`;
return `${options.prefix}${this.state.behind}${GlyphChars.ArrowDown}${options.separator}${this.state.ahead}${
GlyphChars.ArrowUp
}`;
}
}
@ -107,14 +115,13 @@ export interface IGitStatusFileWithCommit extends IGitStatusFile {
}
export class GitStatusFile implements IGitStatusFile {
constructor(
public readonly repoPath: string,
public readonly indexStatus: GitStatusFileStatus,
public readonly workTreeStatus: GitStatusFileStatus,
public readonly fileName: string,
public readonly originalFileName?: string
) { }
) {}
get status(): GitStatusFileStatus {
return (this.indexStatus || this.workTreeStatus || '?') as GitStatusFileStatus;
@ -144,7 +151,12 @@ export class GitStatusFile implements IGitStatusFile {
return GitStatusFile.getStatusText(this.status);
}
with(changes: { indexStatus?: GitStatusFileStatus | null, workTreeStatus?: GitStatusFileStatus | null, fileName?: string, originalFileName?: string | null }): GitStatusFile {
with(changes: {
indexStatus?: GitStatusFileStatus | null;
workTreeStatus?: GitStatusFileStatus | null;
fileName?: string;
originalFileName?: string | null;
}): GitStatusFile {
return new GitStatusFile(
this.repoPath,
this.getChangedValue(changes.indexStatus, this.indexStatus) as GitStatusFileStatus,
@ -159,14 +171,22 @@ export class GitStatusFile implements IGitStatusFile {
return change !== null ? change : undefined;
}
static getFormattedDirectory(status: IGitStatusFile, includeOriginal: boolean = false, relativeTo?: string): string {
static getFormattedDirectory(
status: IGitStatusFile,
includeOriginal: boolean = false,
relativeTo?: string
): string {
const directory = GitUri.getDirectory(status.fileName, relativeTo);
return (includeOriginal && status.status === 'R' && status.originalFileName)
return includeOriginal && status.status === 'R' && status.originalFileName
? `${directory} ${Strings.pad(GlyphChars.ArrowLeft, 1, 1)} ${status.originalFileName}`
: directory;
}
static getFormattedPath(status: IGitStatusFile, separator: string = Strings.pad(GlyphChars.Dot, 2, 2), relativeTo?: string): string {
static getFormattedPath(
status: IGitStatusFile,
separator: string = Strings.pad(GlyphChars.Dot, 2, 2),
relativeTo?: string
): string {
return GitUri.getFormattedPath(status.fileName, separator, relativeTo);
}
@ -231,4 +251,4 @@ const statusTextMap = {
export function getGitStatusText(status: GitStatusFileStatus): string {
return statusTextMap[status] || statusTextMap['X'];
}
}

+ 3
- 6
src/git/models/tag.ts View File

@ -1,21 +1,18 @@
'use strict';
export class GitTag {
constructor(
public readonly repoPath: string,
public readonly name: string
) { }
) {}
private _basename: string | undefined;
getBasename(): string {
if (this._basename === undefined) {
const index = this.name.lastIndexOf('/');
this._basename = index !== -1
? this.name.substring(index + 1)
: this.name;
this._basename = index !== -1 ? this.name.substring(index + 1) : this.name;
}
return this._basename;
}
}
}

+ 31
- 9
src/git/parsers/blameParser.ts View File

@ -24,8 +24,12 @@ interface BlameEntry {
}
export class GitBlameParser {
static parse(data: string, repoPath: string | undefined, fileName: string, currentUser: string | undefined): GitBlame | undefined {
static parse(
data: string,
repoPath: string | undefined,
fileName: string,
currentUser: string | undefined
): GitBlame | undefined {
if (!data) return undefined;
const authors: Map<string, GitAuthor> = new Map();
@ -61,7 +65,10 @@ export class GitBlameParser {
entry.author = 'You';
}
else {
entry.author = lineParts.slice(1).join(' ').trim();
entry.author = lineParts
.slice(1)
.join(' ')
.trim();
if (currentUser !== undefined && currentUser === entry.author) {
entry.author = 'You';
}
@ -69,7 +76,10 @@ export class GitBlameParser {
break;
case 'author-mail':
entry.authorEmail = lineParts.slice(1).join(' ').trim();
entry.authorEmail = lineParts
.slice(1)
.join(' ')
.trim();
const start = entry.authorEmail.indexOf('<');
if (start >= 0) {
const end = entry.authorEmail.indexOf('>', start);
@ -92,7 +102,10 @@ export class GitBlameParser {
break;
case 'summary':
entry.summary = lineParts.slice(1).join(' ').trim();
entry.summary = lineParts
.slice(1)
.join(' ')
.trim();
break;
case 'previous':
@ -105,7 +118,9 @@ export class GitBlameParser {
if (first && repoPath === undefined) {
// Try to get the repoPath from the most recent commit
repoPath = Strings.normalizePath(fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, ''));
repoPath = Strings.normalizePath(
fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, '')
);
relativeFileName = Strings.normalizePath(path.relative(repoPath, fileName));
}
first = false;
@ -139,7 +154,14 @@ export class GitBlameParser {
} as GitBlame;
}
private static parseEntry(entry: BlameEntry, repoPath: string | undefined, fileName: string | undefined, commits: Map<string, GitBlameCommit>, authors: Map<string, GitAuthor>, lines: GitCommitLine[]) {
private static parseEntry(
entry: BlameEntry,
repoPath: string | undefined,
fileName: string | undefined,
commits: Map<string, GitBlameCommit>,
authors: Map<string, GitAuthor>,
lines: GitCommitLine[]
) {
let commit = commits.get(entry.sha);
if (commit === undefined) {
if (entry.author !== undefined) {
@ -158,7 +180,7 @@ export class GitBlameParser {
entry.sha,
entry.author,
entry.authorEmail,
new Date(entry.authorDate as any * 1000),
new Date((entry.authorDate as any) * 1000),
entry.summary!,
fileName!,
fileName !== entry.fileName ? entry.fileName : undefined,
@ -185,4 +207,4 @@ export class GitBlameParser {
lines[line.line] = line;
}
}
}
}

+ 2
- 6
src/git/parsers/branchParser.ts View File

@ -5,7 +5,6 @@ const branchWithTrackingRegex = /^(\*?)\s+(.+?)\s+([0-9,a-f]+)\s+(?:\[(.*?\/.*?)
const branchWithTrackingStateRegex = /^(?:ahead\s([0-9]+))?[,\s]*(?:behind\s([0-9]+))?/;
export class GitBranchParser {
static parse(data: string, repoPath: string): GitBranch[] | undefined {
if (!data) return undefined;
@ -33,9 +32,6 @@ export class GitBranchParser {
const ahead = parseInt(match[1], 10);
const behind = parseInt(match[2], 10);
return [
isNaN(ahead) ? 0 : ahead,
isNaN(behind) ? 0 : behind
];
return [isNaN(ahead) ? 0 : ahead, isNaN(behind) ? 0 : behind];
}
}
}

+ 24
- 4
src/git/parsers/diffParser.ts View File

@ -1,13 +1,21 @@
'use strict';
import { Iterables, Strings } from '../../system';
import { GitDiff, GitDiffChunk, GitDiffChunkLine, GitDiffLine, GitDiffShortStat, GitStatusFile, GitStatusParser } from './../git';
import {
GitDiff,
GitDiffChunk,
GitDiffChunkLine,
GitDiffLine,
GitDiffShortStat,
GitStatusFile,
GitStatusParser
} from './../git';
const nameStatusDiffRegex = /^(.*?)\t(.*?)(?:\t(.*?))?$/gm;
// tslint:disable-next-line:max-line-length
const shortStatDiffRegex = /^\s*(\d+)\sfiles? changed(?:,\s+(\d+)\s+insertions?\+)?(?:,\s+(\d+)\s+deletions?)?/;
const unifiedDiffRegex = /^@@ -([\d]+),([\d]+) [+]([\d]+),([\d]+) @@([\s\S]*?)(?=^@@)/gm;
export class GitDiffParser {
static parse(data: string, debug: boolean = false): GitDiff | undefined {
if (!data) return undefined;
@ -29,7 +37,19 @@ export class GitDiffParser {
currentStart = parseInt(match[3], 10);
previousStart = parseInt(match[1], 10);
chunks.push(new GitDiffChunk(chunk, { start: currentStart, end: currentStart + parseInt(match[4], 10) }, { start: previousStart, end: previousStart + parseInt(match[2], 10) }));
chunks.push(
new GitDiffChunk(
chunk,
{
start: currentStart,
end: currentStart + parseInt(match[4], 10)
},
{
start: previousStart,
end: previousStart + parseInt(match[2], 10)
}
)
);
} while (match != null);
if (!chunks.length) return undefined;
@ -152,4 +172,4 @@ export class GitDiffParser {
deletions: deletions == null ? 0 : parseInt(deletions, 10)
} as GitDiffShortStat;
}
}
}

+ 49
- 12
src/git/parsers/logParser.ts View File

@ -27,8 +27,17 @@ const diffRegex = /diff --git a\/(.*) b\/(.*)/;
const emptyEntry: LogEntry = {};
export class GitLogParser {
static parse(data: string, type: GitCommitType, repoPath: string | undefined, fileName: string | undefined, sha: string | undefined, currentUser: string | undefined, maxCount: number | undefined, reverse: boolean, range: Range | undefined): GitLog | undefined {
static parse(
data: string,
type: GitCommitType,
repoPath: string | undefined,
fileName: string | undefined,
sha: string | undefined,
currentUser: string | undefined,
maxCount: number | undefined,
reverse: boolean,
range: Range | undefined
): GitLog | undefined {
if (!data) return undefined;
let relativeFileName: string;
@ -60,7 +69,7 @@ export class GitLogParser {
line = next.value;
// Since log --reverse doesn't properly honor a max count -- enforce it here
if (reverse && maxCount && (i >= maxCount)) break;
if (reverse && maxCount && i >= maxCount) break;
// <<1-char token>> <data>
// e.g. <r> bd1452a2dc
@ -173,13 +182,17 @@ export class GitLogParser {
}
if (entry.fileStatuses !== undefined) {
entry.fileName = Arrays.filterMap(entry.fileStatuses,
f => !!f.fileName ? f.fileName : undefined).join(', ');
entry.fileName = Arrays.filterMap(
entry.fileStatuses,
f => (!!f.fileName ? f.fileName : undefined)
).join(', ');
}
if (first && repoPath === undefined && type === GitCommitType.File && fileName !== undefined) {
// Try to get the repoPath from the most recent commit
repoPath = Strings.normalizePath(fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, ''));
repoPath = Strings.normalizePath(
fileName.replace(fileName.startsWith('/') ? `/${entry.fileName}` : entry.fileName!, '')
);
relativeFileName = Strings.normalizePath(path.relative(repoPath, fileName));
}
else {
@ -191,7 +204,16 @@ export class GitLogParser {
if (commit === undefined) {
i++;
}
recentCommit = GitLogParser.parseEntry(entry, commit, type, repoPath, relativeFileName, commits, authors, recentCommit);
recentCommit = GitLogParser.parseEntry(
entry,
commit,
type,
repoPath,
relativeFileName,
commits,
authors,
recentCommit
);
break;
}
@ -209,7 +231,16 @@ export class GitLogParser {
} as GitLog;
}
private static parseEntry(entry: LogEntry, commit: GitLogCommit | undefined, type: GitCommitType, repoPath: string | undefined, relativeFileName: string, commits: Map<string, GitLogCommit>, authors: Map<string, GitAuthor>, recentCommit: GitLogCommit | undefined): GitLogCommit | undefined {
private static parseEntry(
entry: LogEntry,
commit: GitLogCommit | undefined,
type: GitCommitType,
repoPath: string | undefined,
relativeFileName: string,
commits: Map<string, GitLogCommit>,
authors: Map<string, GitAuthor>,
recentCommit: GitLogCommit | undefined
): GitLogCommit | undefined {
if (commit === undefined) {
if (entry.author !== undefined) {
let author = authors.get(entry.author);
@ -224,7 +255,13 @@ export class GitLogParser {
const originalFileName = relativeFileName !== entry.fileName ? entry.fileName : undefined;
if (type === GitCommitType.File) {
entry.fileStatuses = [{ status: entry.status, fileName: relativeFileName, originalFileName: originalFileName } as IGitStatusFile];
entry.fileStatuses = [
{
status: entry.status,
fileName: relativeFileName,
originalFileName: originalFileName
} as IGitStatusFile
];
}
commit = new GitLogCommit(
@ -233,7 +270,7 @@ export class GitLogParser {
entry.ref!,
entry.author!,
entry.email,
new Date(entry.date! as any * 1000),
new Date((entry.date! as any) * 1000),
entry.summary === undefined ? '' : entry.summary,
relativeFileName,
entry.fileStatuses || [],
@ -265,7 +302,7 @@ export class GitLogParser {
return commit;
}
static parseFileName(entry: { fileName?: string, originalFileName?: string }) {
static parseFileName(entry: { fileName?: string; originalFileName?: string }) {
if (entry.fileName === undefined) return;
const index = entry.fileName.indexOf('\t') + 1;
@ -280,4 +317,4 @@ export class GitLogParser {
}
}
}
}
}

+ 10
- 4
src/git/parsers/remoteParser.ts View File

@ -4,6 +4,7 @@ import { GitRemoteType } from '../models/remote';
import { RemoteProvider } from '../remotes/factory';
const remoteRegex = /^(.*)\t(.*)\s(.)$/gm;
// tslint:disable-next-line:max-line-length
const urlRegex = /^(?:(git:\/\/)(.*?)\/|(https?:\/\/)(?:.*?@)?(.*?)\/|git@(.*):|(ssh:\/\/)(?:.*@)?(.*?)(?::.*?)?\/|(?:.*?@)(.*?):)(.*)$/;
// Test git urls
@ -44,8 +45,11 @@ user:password@host.xz:/path/to/repo.git/
*/
export class GitRemoteParser {
static parse(data: string, repoPath: string, providerFactory: (domain: string, path: string) => RemoteProvider | undefined): GitRemote[] {
static parse(
data: string,
repoPath: string,
providerFactory: (domain: string, path: string) => RemoteProvider | undefined
): GitRemote[] {
if (!data) return [];
const remotes: GitRemote[] = [];
@ -63,7 +67,9 @@ export class GitRemoteParser {
const uniqueness = `${domain}/${path}`;
let remote: GitRemote | undefined = groups[uniqueness];
if (remote === undefined) {
remote = new GitRemote(repoPath, match[1], scheme, domain, path, providerFactory(domain, path), [{ url: url, type: match[3] as GitRemoteType }]);
remote = new GitRemote(repoPath, match[1], scheme, domain, path, providerFactory(domain, path), [
{ url: url, type: match[3] as GitRemoteType }
]);
remotes.push(remote);
groups[uniqueness] = remote;
}
@ -87,4 +93,4 @@ export class GitRemoteParser {
match[9].replace(/\.git\/?$/, '')
];
}
}
}

+ 12
- 6
src/git/parsers/stashParser.ts View File

@ -15,7 +15,6 @@ interface StashEntry {
const emptyEntry: StashEntry = {};
export class GitStashParser {
static parse(data: string, repoPath: string): GitStash | undefined {
if (!data) return undefined;
@ -110,8 +109,10 @@ export class GitStashParser {
}
if (entry.fileStatuses !== undefined) {
entry.fileNames = Arrays.filterMap(entry.fileStatuses,
f => !!f.fileName ? f.fileName : undefined).join(', ');
entry.fileNames = Arrays.filterMap(
entry.fileStatuses,
f => (!!f.fileName ? f.fileName : undefined)
).join(', ');
}
}
@ -126,14 +127,19 @@ export class GitStashParser {
} as GitStash;
}
private static parseEntry(entry: StashEntry, commit: GitStashCommit | undefined, repoPath: string, commits: Map<string, GitStashCommit>): GitStashCommit | undefined {
private static parseEntry(
entry: StashEntry,
commit: GitStashCommit | undefined,
repoPath: string,
commits: Map<string, GitStashCommit>
): GitStashCommit | undefined {
if (commit === undefined) {
commit = new GitStashCommit(
GitCommitType.Stash,
entry.stashName!,
repoPath,
entry.ref!,
new Date(entry.date! as any * 1000),
new Date((entry.date! as any) * 1000),
entry.summary === undefined ? '' : entry.summary,
entry.fileNames!,
entry.fileStatuses || []
@ -143,4 +149,4 @@ export class GitStashParser {
commits.set(entry.ref!, commit);
return commit;
}
}
}

+ 13
- 19
src/git/parsers/statusParser.ts View File

@ -6,7 +6,6 @@ const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
const behindStatusV1Regex = /(?:behind ([0-9]+))/;
export class GitStatusParser {
static parse(data: string, repoPath: string, porcelainVersion: number): GitStatus | undefined {
if (!data) return undefined;
@ -57,13 +56,7 @@ export class GitStatusParser {
}
}
return new GitStatus(
Strings.normalizePath(repoPath),
branch || '',
'',
files,
state,
upstream);
return new GitStatus(Strings.normalizePath(repoPath), branch || '', '', files, state, upstream);
}
private static parseV2(lines: string[], repoPath: string): GitStatus {
@ -105,7 +98,10 @@ export class GitStatusParser {
files.push(this.parseStatusFile(repoPath, lineParts[1], lineParts.slice(8).join(' ')));
break;
case '2': // rename
const file = lineParts.slice(9).join(' ').split('\t');
const file = lineParts
.slice(9)
.join(' ')
.split('\t');
files.push(this.parseStatusFile(repoPath, lineParts[1], file[0], file[1]));
break;
case 'u': // unmerged
@ -118,17 +114,15 @@ export class GitStatusParser {
}
}
return new GitStatus(
Strings.normalizePath(repoPath),
branch || '',
sha || '',
files,
state,
upstream
);
return new GitStatus(Strings.normalizePath(repoPath), branch || '', sha || '', files, state, upstream);
}
static parseStatusFile(repoPath: string, rawStatus: string, fileName: string, originalFileName?: string): GitStatusFile {
static parseStatusFile(
repoPath: string,
rawStatus: string,
fileName: string,
originalFileName?: string
): GitStatusFile {
let indexStatus = rawStatus[0] !== '.' ? rawStatus[0].trim() : undefined;
if (indexStatus === '' || indexStatus === null) {
indexStatus = undefined;
@ -150,4 +144,4 @@ export class GitStatusParser {
originalFileName
);
}
}
}

+ 2
- 3
src/git/parsers/tagParser.ts View File

@ -3,13 +3,12 @@ import { Arrays } from '../../system';
import { GitTag } from './../git';
export class GitTagParser {
static parse(data: string, repoPath: string): GitTag[] | undefined {
if (!data) return undefined;
const tags = Arrays.filterMap(data.split('\n'), t => !!t ? new GitTag(repoPath, t) : undefined);
const tags = Arrays.filterMap(data.split('\n'), t => (!!t ? new GitTag(repoPath, t) : undefined));
if (!tags.length) return undefined;
return tags;
}
}
}

+ 9
- 14
src/git/remotes/bitbucket-server.ts View File

@ -6,14 +6,7 @@ const issueEnricherRegEx = /(^|\s)(issue #([0-9]+))\b/gi;
const prEnricherRegEx = /(^|\s)(pull request #([0-9]+))\b/gi;
export class BitbucketServerService extends RemoteProvider {
constructor(
domain: string,
path: string,
protocol?: string,
name?: string,
custom: boolean = false
) {
constructor(domain: string, path: string, protocol?: string, name?: string, custom: boolean = false) {
super(domain, path, protocol, name, custom);
}
@ -31,11 +24,13 @@ export class BitbucketServerService extends RemoteProvider {
}
enrichMessage(message: string): string {
return message
// Matches issue #123
.replace(issueEnricherRegEx, `$1[$2](${this.baseUrl}/issues/$3 "Open Issue $2")`)
// Matches pull request #123
.replace(prEnricherRegEx, `$1[$2](${this.baseUrl}/pull-requests/$3 "Open PR $2")`);
return (
message
// Matches issue #123
.replace(issueEnricherRegEx, `$1[$2](${this.baseUrl}/issues/$3 "Open Issue $2")`)
// Matches pull request #123
.replace(prEnricherRegEx, `$1[$2](${this.baseUrl}/pull-requests/$3 "Open PR $2")`)
);
}
protected getUrlForBranches(): string {
@ -65,4 +60,4 @@ export class BitbucketServerService extends RemoteProvider {
if (branch) return `${this.baseUrl}/browse/${fileName}?at=${branch}${line}`;
return `${this.baseUrl}/browse/${fileName}${line}`;
}
}
}

Some files were not shown because too many files changed in this diff

||||||
x
 
000:0
Loading…
Cancel
Save