3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
- import { DecorationOptions , l10n , Position , Range , TextEditor , TextEditorChange , TextEditorDecorationType , TextEditorChangeKind , ThemeColor , Uri , window , workspace , EventEmitter , ConfigurationChangeEvent , StatusBarItem , StatusBarAlignment , Command , MarkdownString , commands } from 'vscode' ;
6
+ import { DecorationOptions , l10n , Position , Range , TextEditor , TextEditorChange , TextEditorDecorationType , TextEditorChangeKind , ThemeColor , Uri , window , workspace , EventEmitter , ConfigurationChangeEvent , StatusBarItem , StatusBarAlignment , Command , MarkdownString , QuickDiffProvider } from 'vscode' ;
7
7
import { Model } from './model' ;
8
8
import { dispose , fromNow , IDisposable , pathEquals } from './util' ;
9
9
import { Repository } from './repository' ;
10
10
import { throttle } from './decorators' ;
11
11
import { BlameInformation } from './git' ;
12
+ import { toGitUri } from './uri' ;
12
13
13
14
function lineRangesContainLine ( changes : readonly TextEditorChange [ ] , lineNumber : number ) : boolean {
14
15
return changes . some ( c => c . modified . startLineNumber <= lineNumber && lineNumber < c . modified . endLineNumberExclusive ) ;
@@ -18,23 +19,6 @@ function lineRangeLength(startLineNumber: number, endLineNumberExclusive: number
18
19
return endLineNumberExclusive - startLineNumber ;
19
20
}
20
21
21
- function toTextEditorChange ( originalStartLineNumber : number , originalEndLineNumberExclusive : number , modifiedStartLineNumber : number , modifiedEndLineNumberExclusive : number ) : TextEditorChange {
22
- let kind : TextEditorChangeKind ;
23
- if ( originalStartLineNumber === originalEndLineNumberExclusive ) {
24
- kind = TextEditorChangeKind . Addition ;
25
- } else if ( modifiedStartLineNumber === modifiedEndLineNumberExclusive ) {
26
- kind = TextEditorChangeKind . Deletion ;
27
- } else {
28
- kind = TextEditorChangeKind . Modification ;
29
- }
30
-
31
- return {
32
- original : { startLineNumber : originalStartLineNumber , endLineNumberExclusive : originalEndLineNumberExclusive } ,
33
- modified : { startLineNumber : modifiedStartLineNumber , endLineNumberExclusive : modifiedEndLineNumberExclusive } ,
34
- kind
35
- } ;
36
- }
37
-
38
22
function mapModifiedLineNumberToOriginalLineNumber ( lineNumber : number , changes : readonly TextEditorChange [ ] ) : number {
39
23
if ( changes . length === 0 ) {
40
24
return lineNumber ;
@@ -79,14 +63,13 @@ interface LineBlameInformation {
79
63
readonly blameInformation : BlameInformation | string ;
80
64
}
81
65
82
- export class GitBlameController {
66
+ export class GitBlameController implements QuickDiffProvider {
83
67
private readonly _onDidChangeBlameInformation = new EventEmitter < TextEditor > ( ) ;
84
68
public readonly onDidChangeBlameInformation = this . _onDidChangeBlameInformation . event ;
85
69
86
70
readonly textEditorBlameInformation = new Map < TextEditor , readonly LineBlameInformation [ ] > ( ) ;
87
71
88
72
private readonly _repositoryBlameInformation = new Map < Repository , RepositoryBlameInformation > ( ) ;
89
- private readonly _stagedResourceDiffInformation = new Map < Repository , Map < Uri , TextEditorChange [ ] > > ( ) ;
90
73
91
74
private _repositoryDisposables = new Map < Repository , IDisposable [ ] > ( ) ;
92
75
private _disposables : IDisposable [ ] = [ ] ;
@@ -104,6 +87,25 @@ export class GitBlameController {
104
87
this . _updateTextEditorBlameInformation ( window . activeTextEditor ) ;
105
88
}
106
89
90
+ get visible ( ) : boolean {
91
+ return false ;
92
+ }
93
+
94
+ provideOriginalResource ( uri : Uri ) : Uri | undefined {
95
+ // Ignore resources outside a repository
96
+ const repository = this . _model . getRepository ( uri ) ;
97
+ if ( ! repository ) {
98
+ return undefined ;
99
+ }
100
+
101
+ // Ignore resources that are not in the index group
102
+ if ( ! repository . indexGroup . resourceStates . some ( r => pathEquals ( r . resourceUri . fsPath , uri . fsPath ) ) ) {
103
+ return undefined ;
104
+ }
105
+
106
+ return toGitUri ( uri , 'HEAD' , { replaceFileExtension : true } ) ;
107
+ }
108
+
107
109
getBlameInformationHover ( documentUri : Uri , blameInformation : BlameInformation | string ) : MarkdownString {
108
110
if ( typeof blameInformation === 'string' ) {
109
111
return new MarkdownString ( blameInformation , true ) ;
@@ -141,9 +143,7 @@ export class GitBlameController {
141
143
142
144
private _onDidOpenRepository ( repository : Repository ) : void {
143
145
const repositoryDisposables : IDisposable [ ] = [ ] ;
144
-
145
146
repository . onDidRunGitStatus ( ( ) => this . _onDidRunGitStatus ( repository ) , this , repositoryDisposables ) ;
146
- repository . onDidChangeRepository ( e => this . _onDidChangeRepository ( repository , e ) , this , this . _disposables ) ;
147
147
148
148
this . _repositoryDisposables . set ( repository , repositoryDisposables ) ;
149
149
}
@@ -174,14 +174,6 @@ export class GitBlameController {
174
174
}
175
175
}
176
176
177
- private _onDidChangeRepository ( repository : Repository , uri : Uri ) : void {
178
- if ( ! / \. g i t \/ i n d e x $ / . test ( uri . fsPath ) ) {
179
- return ;
180
- }
181
-
182
- this . _stagedResourceDiffInformation . delete ( repository ) ;
183
- }
184
-
185
177
private async _getBlameInformation ( resource : Uri ) : Promise < BlameInformation [ ] | undefined > {
186
178
const repository = this . _model . getRepository ( resource ) ;
187
179
if ( ! repository || ! repository . HEAD ?. commit ) {
@@ -209,76 +201,65 @@ export class GitBlameController {
209
201
return resourceBlameInformation ;
210
202
}
211
203
212
- private async _getStagedResourceDiffInformation ( uri : Uri ) : Promise < TextEditorChange [ ] | undefined > {
213
- const repository = this . _model . getRepository ( uri ) ;
214
- if ( ! repository ) {
215
- return undefined ;
204
+ @ throttle
205
+ private async _updateTextEditorBlameInformation ( textEditor : TextEditor | undefined ) : Promise < void > {
206
+ if ( ! textEditor ?. diffInformation ) {
207
+ return ;
216
208
}
217
209
218
- const [ resource ] = repository . indexGroup
219
- . resourceStates . filter ( r => pathEquals ( uri . fsPath , r . resourceUri . fsPath ) ) ;
220
-
221
- if ( ! resource || ! resource . leftUri || ! resource . rightUri ) {
222
- return undefined ;
223
- }
210
+ // Working tree diff information
211
+ const diffInformationWorkingTree = textEditor . diffInformation
212
+ . filter ( diff => diff . original ?. scheme === 'git' )
213
+ . find ( diff => {
214
+ const query = JSON . parse ( diff . original ! . query ) as { ref : string } ;
215
+ return query . ref !== 'HEAD' ;
216
+ } ) ;
224
217
225
- const diffInformationMap = this . _stagedResourceDiffInformation . get ( repository ) ?? new Map < Uri , TextEditorChange [ ] > ( ) ;
226
- let changes = diffInformationMap . get ( resource . resourceUri ) ;
227
- if ( changes ) {
228
- return changes ;
229
- }
218
+ // Working tree + index diff information
219
+ const diffInformationWorkingTreeAndIndex = textEditor . diffInformation
220
+ . filter ( diff => diff . original ?. scheme === 'git' )
221
+ . find ( diff => {
222
+ const query = JSON . parse ( diff . original ! . query ) as { ref : string } ;
223
+ return query . ref === 'HEAD' ;
224
+ } ) ;
230
225
231
- // Get the diff information for the staged resource
232
- const diffInformation : [ number , number , number , number ] [ ] = await commands . executeCommand ( '_workbench.internal.computeDiff' , resource . leftUri , resource . rightUri ) ;
233
- if ( ! diffInformation ) {
234
- return undefined ;
226
+ // Working tree diff information is not present or it is stale
227
+ if ( ! diffInformationWorkingTree || diffInformationWorkingTree . isStale ) {
228
+ return ;
235
229
}
236
230
237
- changes = diffInformation . map ( change => toTextEditorChange ( change [ 0 ] , change [ 1 ] , change [ 2 ] , change [ 3 ] ) ) ;
238
- this . _stagedResourceDiffInformation . set ( repository , diffInformationMap . set ( resource . resourceUri , changes ) ) ;
239
-
240
- return changes ;
241
- }
242
-
243
- @throttle
244
- private async _updateTextEditorBlameInformation ( textEditor : TextEditor | undefined ) : Promise < void > {
245
- const diffInformation = textEditor ?. diffInformation ;
246
- if ( ! diffInformation || diffInformation . isStale ) {
231
+ // Working tree + index diff information is present and it is stale
232
+ if ( diffInformationWorkingTreeAndIndex && diffInformationWorkingTreeAndIndex . isStale ) {
247
233
return ;
248
234
}
249
235
236
+ // For staged resources, we provide an additional "original resource" so that core can
237
+ // compute the diff information that contains the changes from the working tree and the
238
+ // index.
239
+ const diffInformation = diffInformationWorkingTreeAndIndex ?? diffInformationWorkingTree ;
240
+
241
+ // Git blame information
250
242
const resourceBlameInformation = await this . _getBlameInformation ( textEditor . document . uri ) ;
251
243
if ( ! resourceBlameInformation ) {
252
244
return ;
253
245
}
254
246
255
- // The diff information does not contain changes that have been staged. We need
256
- // to get the staged changes and if present, merge them with the diff information.
257
- const diffInformationStagedResources : TextEditorChange [ ] = await this . _getStagedResourceDiffInformation ( textEditor . document . uri ) ?? [ ] ;
258
-
259
- console . log ( 'diffInformation' , diffInformation . changes ) ;
260
- console . log ( 'diffInformationStagedResources' , diffInformationStagedResources ) ;
261
-
262
- console . log ( 'resourceBlameInformation' , resourceBlameInformation ) ;
263
-
264
247
const lineBlameInformation : LineBlameInformation [ ] = [ ] ;
265
248
for ( const lineNumber of textEditor . selections . map ( s => s . active . line ) ) {
266
- // Check if the line is contained in the diff information
267
- if ( lineRangesContainLine ( diffInformation . changes , lineNumber + 1 ) ) {
249
+ // Check if the line is contained in the working tree diff information
250
+ if ( lineRangesContainLine ( diffInformationWorkingTree . changes , lineNumber + 1 ) ) {
268
251
lineBlameInformation . push ( { lineNumber, blameInformation : l10n . t ( 'Not Committed Yet' ) } ) ;
269
252
continue ;
270
253
}
271
254
272
- // Check if the line is contained in the staged resources diff information
273
- if ( lineRangesContainLine ( diffInformationStagedResources , lineNumber + 1 ) ) {
255
+ // Check if the line is contained in the working tree + index diff information
256
+ if ( lineRangesContainLine ( diffInformationWorkingTreeAndIndex ?. changes ?? [ ] , lineNumber + 1 ) ) {
274
257
lineBlameInformation . push ( { lineNumber, blameInformation : l10n . t ( 'Not Committed Yet (Staged)' ) } ) ;
275
258
continue ;
276
259
}
277
260
278
- const diffInformationAll = [ ...diffInformation . changes , ...diffInformationStagedResources ] ;
279
-
280
261
// Map the line number to the git blame ranges using the diff information
281
- const lineNumberWithDiff = mapModifiedLineNumberToOriginalLineNumber ( lineNumber + 1 , diffInformationAll ) ;
262
+ const lineNumberWithDiff = mapModifiedLineNumberToOriginalLineNumber ( lineNumber + 1 , diffInformation . changes ) ;
282
263
const blameInformation = resourceBlameInformation . find ( blameInformation => {
283
264
return blameInformation . ranges . find ( range => {
284
265
return lineNumberWithDiff >= range . startLineNumber && lineNumberWithDiff <= range . endLineNumber ;
0 commit comments