1+ import compareDesc from 'date-fns/compareDesc'
12import formatDistanceStrict from 'date-fns/formatDistanceStrict'
2- import { Selection , TextDocumentDecoration } from 'sourcegraph'
3+ import { Selection , StatusBarItem , TextDocumentDecoration } from 'sourcegraph'
34import gql from 'tagged-template-noop'
45import { Settings } from './extension'
56import { resolveURI } from './uri'
67import { memoizeAsync } from './util/memoizeAsync'
78
8- export const getDecorationFromHunk = (
9- { message, author, commit } : Hunk ,
9+ /**
10+ * Get display info shared between status bar items and text document decorations.
11+ */
12+ const getDisplayInfoFromHunk = (
13+ { author, commit, message } : Pick < Hunk , 'author' | 'commit' | 'message' > ,
1014 now : number ,
11- decoratedLine : number ,
1215 sourcegraph : typeof import ( 'sourcegraph' )
13- ) : TextDocumentDecoration => {
16+ ) : { displayName : string ; username : string ; distance : string ; linkURL : string ; hoverMessage : string } => {
1417 const displayName = truncate ( author . person . displayName , 25 )
1518 const username = author . person . user ? `(${ author . person . user . username } ) ` : ''
1619 const distance = formatDistanceStrict ( author . date , now , { addSuffix : true } )
20+ const linkURL = new URL ( commit . url , sourcegraph . internal . sourcegraphURL . toString ( ) ) . href
21+ const hoverMessage = `${ author . person . email } • ${ truncate ( message , 1000 ) } `
22+
1723 return {
18- range : new sourcegraph . Range ( decoratedLine , 0 , decoratedLine , 0 ) ,
19- isWholeLine : true ,
20- after : {
21- light : {
22- color : 'rgba(0, 0, 25, 0.55)' ,
23- backgroundColor : 'rgba(193, 217, 255, 0.65)' ,
24- } ,
25- dark : {
26- color : 'rgba(235, 235, 255, 0.55)' ,
27- backgroundColor : 'rgba(15, 43, 89, 0.65)' ,
28- } ,
29- contentText : `${ username } ${ displayName } , ${ distance } : • ${ truncate ( message , 45 ) } ` ,
30- hoverMessage : `${ author . person . email } • ${ truncate ( message , 1000 ) } ` ,
31- linkURL : new URL ( commit . url , sourcegraph . internal . sourcegraphURL . toString ( ) ) . href ,
32- } ,
24+ displayName,
25+ username,
26+ distance,
27+ linkURL,
28+ hoverMessage,
3329 }
3430}
3531
36- export const getBlameDecorationsForSelections = (
32+ /**
33+ * Get hunks and 0-indexed start lines for the given selections.
34+ *
35+ * @param selections If null, returns all hunks
36+ */
37+ export const getHunksForSelections = (
3738 hunks : Hunk [ ] ,
38- selections : Selection [ ] ,
39- now : number ,
40- sourcegraph : typeof import ( 'sourcegraph' )
41- ) => {
42- const decorations : TextDocumentDecoration [ ] = [ ]
39+ selections : Selection [ ] | null
40+ ) : { selectionStartLine : number ; hunk : Hunk } [ ] => {
41+ const hunksForSelections : { selectionStartLine : number ; hunk : Hunk } [ ] = [ ]
42+
43+ if ( ! selections ) {
44+ return hunks . map ( hunk => ( { hunk, selectionStartLine : hunk . startLine - 1 } ) )
45+ }
46+
4347 for ( const hunk of hunks ) {
4448 // Hunk start and end lines are 1-indexed, but selection lines are zero-indexed
4549 const hunkStartLineZeroBased = hunk . startLine - 1
@@ -50,20 +54,59 @@ export const getBlameDecorationsForSelections = (
5054 if ( selection . end . line < hunkStartLineZeroBased || selection . start . line > hunkEndLineZeroBased ) {
5155 continue
5256 }
57+
5358 // Decorate the hunk's start line or, if the hunk's start line is
5459 // outside of the selection's boundaries, the start line of the selection.
55- const decoratedLine =
60+ const selectionStartLine =
5661 hunkStartLineZeroBased < selection . start . line ? selection . start . line : hunkStartLineZeroBased
57- decorations . push ( getDecorationFromHunk ( hunk , now , decoratedLine , sourcegraph ) )
62+ hunksForSelections . push ( { selectionStartLine , hunk } )
5863 }
5964 }
60- return decorations
65+
66+ return hunksForSelections
67+ }
68+
69+ export const getDecorationFromHunk = (
70+ hunk : Hunk ,
71+ now : number ,
72+ decoratedLine : number ,
73+ sourcegraph : typeof import ( 'sourcegraph' )
74+ ) : TextDocumentDecoration => {
75+ const { displayName, username, distance, linkURL, hoverMessage } = getDisplayInfoFromHunk ( hunk , now , sourcegraph )
76+
77+ return {
78+ range : new sourcegraph . Range ( decoratedLine , 0 , decoratedLine , 0 ) ,
79+ isWholeLine : true ,
80+ after : {
81+ light : {
82+ color : 'rgba(0, 0, 25, 0.55)' ,
83+ backgroundColor : 'rgba(193, 217, 255, 0.65)' ,
84+ } ,
85+ dark : {
86+ color : 'rgba(235, 235, 255, 0.55)' ,
87+ backgroundColor : 'rgba(15, 43, 89, 0.65)' ,
88+ } ,
89+ contentText : `${ username } ${ displayName } , ${ distance } : • ${ truncate ( hunk . message , 45 ) } ` ,
90+ hoverMessage,
91+ linkURL,
92+ } ,
93+ }
6194}
6295
96+ export const getBlameDecorationsForSelections = (
97+ hunks : Hunk [ ] ,
98+ selections : Selection [ ] ,
99+ now : number ,
100+ sourcegraph : typeof import ( 'sourcegraph' )
101+ ) =>
102+ getHunksForSelections ( hunks , selections ) . map ( ( { hunk, selectionStartLine } ) =>
103+ getDecorationFromHunk ( hunk , now , selectionStartLine , sourcegraph )
104+ )
105+
63106export const getAllBlameDecorations = ( hunks : Hunk [ ] , now : number , sourcegraph : typeof import ( 'sourcegraph' ) ) =>
64107 hunks . map ( hunk => getDecorationFromHunk ( hunk , now , hunk . startLine - 1 , sourcegraph ) )
65108
66- const queryBlameHunks = memoizeAsync (
109+ export const queryBlameHunks = memoizeAsync (
67110 async ( { uri, sourcegraph } : { uri : string ; sourcegraph : typeof import ( 'sourcegraph' ) } ) : Promise < Hunk [ ] > => {
68111 const { repo, rev, path } = resolveURI ( uri )
69112 const { data, errors } = await sourcegraph . commands . executeCommand (
@@ -111,39 +154,93 @@ const queryBlameHunks = memoizeAsync(
111154)
112155
113156/**
114- * Queries the blame hunks for the document at the provided URI,
115- * and returns blame decorations for all provided selections,
157+ * Returns blame decorations for all provided selections,
116158 * or for all hunks if `selections` is `null`.
117- *
118159 */
119- export const getBlameDecorations = async ( {
120- uri,
160+ export const getBlameDecorations = ( {
121161 settings,
122162 selections,
123163 now,
124- queryHunks = queryBlameHunks ,
164+ hunks ,
125165 sourcegraph,
126166} : {
127- uri : string
128167 settings : Settings
129168 selections : Selection [ ] | null
130169 now : number
131- queryHunks ?: ( { uri , sourcegraph } : { uri : string ; sourcegraph : typeof import ( 'sourcegraph' ) } ) => Promise < Hunk [ ] >
170+ hunks : Hunk [ ]
132171 sourcegraph : typeof import ( 'sourcegraph' )
133- } ) : Promise < TextDocumentDecoration [ ] > => {
172+ } ) : TextDocumentDecoration [ ] => {
134173 const decorations = settings [ 'git.blame.decorations' ] || 'none'
135174
136175 if ( decorations === 'none' ) {
137176 return [ ]
138177 }
139- const hunks = await queryHunks ( { uri, sourcegraph } )
140178 if ( selections !== null && decorations === 'line' ) {
141179 return getBlameDecorationsForSelections ( hunks , selections , now , sourcegraph )
142180 } else {
143181 return getAllBlameDecorations ( hunks , now , sourcegraph )
144182 }
145183}
146184
185+ export const getBlameStatusBarItem = ( {
186+ selections,
187+ hunks,
188+ now,
189+ sourcegraph,
190+ } : {
191+ selections : Selection [ ] | null
192+ hunks : Hunk [ ]
193+ now : number
194+ sourcegraph : typeof import ( 'sourcegraph' )
195+ } ) : StatusBarItem => {
196+ if ( selections && selections . length > 0 ) {
197+ const hunksForSelections = getHunksForSelections ( hunks , selections )
198+ if ( hunksForSelections [ 0 ] ) {
199+ // Display the commit for the first selected hunk in the status bar.
200+ const { displayName, username, distance, linkURL, hoverMessage } = getDisplayInfoFromHunk (
201+ hunksForSelections [ 0 ] . hunk ,
202+ now ,
203+ sourcegraph
204+ )
205+
206+ return {
207+ text : `Blame: ${ username } ${ displayName } , ${ distance } ` ,
208+ command : { id : 'open' , args : [ linkURL ] } ,
209+ tooltip : hoverMessage ,
210+ }
211+ }
212+ }
213+
214+ // Since there are no selections, we want to determine the most
215+ // recent change to this file to display in the status bar.
216+
217+ // Get all hunks
218+ const hunksForSelections = getHunksForSelections ( hunks , null )
219+ const mostRecentHunk = hunksForSelections . sort ( ( a , b ) => compareDesc ( a . hunk . author . date , b . hunk . author . date ) ) [ 0 ]
220+ if ( ! mostRecentHunk ) {
221+ // Probably a network error
222+ return {
223+ text : 'Blame: not found' ,
224+ }
225+ }
226+ const { displayName, username, distance, linkURL, hoverMessage } = getDisplayInfoFromHunk (
227+ mostRecentHunk . hunk ,
228+ now ,
229+ sourcegraph
230+ )
231+
232+ return {
233+ text : `Blame: ${ username } ${ displayName } , ${ distance } ` ,
234+ command : { id : 'open' , args : [ linkURL ] } ,
235+ tooltip : hoverMessage ,
236+ }
237+ }
238+
239+ export interface HunkForSelection {
240+ hunk : Hunk
241+ selectionStartLine : number
242+ }
243+
147244export interface Hunk {
148245 startLine : number
149246 endLine : number
0 commit comments