@@ -15,17 +15,21 @@ import { HoverMerged } from '@sourcegraph/codeintellify/lib/types'
15
15
import { toPrettyBlobURL } from '@sourcegraph/codeintellify/lib/url'
16
16
import * as React from 'react'
17
17
import { render } from 'react-dom'
18
- import { animationFrameScheduler , Observable , of , Subject , Subscription } from 'rxjs'
18
+ import { animationFrameScheduler , BehaviorSubject , Observable , of , Subject , Subscription } from 'rxjs'
19
19
import { filter , map , mergeMap , observeOn , withLatestFrom } from 'rxjs/operators'
20
20
21
- import { createJumpURLFetcher } from '../../shared/backend/lsp'
22
- import { lspViaAPIXlang } from '../../shared/backend/lsp'
21
+ import { TextDocumentItem } from 'sourcegraph/module/client/types/textDocument'
22
+ import { Disposable } from 'vscode-jsonrpc'
23
+ import { createJumpURLFetcher , createLSPFromExtensions } from '../../shared/backend/lsp'
24
+ import { lspViaAPIXlang , toTextDocumentIdentifier } from '../../shared/backend/lsp'
23
25
import { ButtonProps , CodeViewToolbar } from '../../shared/components/CodeViewToolbar'
24
- import { eventLogger , sourcegraphUrl } from '../../shared/util/context'
26
+ import { AbsoluteRepoFile } from '../../shared/repo'
27
+ import { eventLogger , getModeFromPath , sourcegraphUrl , useExtensions } from '../../shared/util/context'
25
28
import { githubCodeHost } from '../github/code_intelligence'
26
29
import { gitlabCodeHost } from '../gitlab/code_intelligence'
27
30
import { phabricatorCodeHost } from '../phabricator/code_intelligence'
28
- import { findCodeViews } from './code_views'
31
+ import { findCodeViews , getContentOfCodeView } from './code_views'
32
+ import { applyDecoration , Controllers , initializeExtensions } from './extensions'
29
33
import { initSearch , SearchFeature } from './search'
30
34
31
35
/**
@@ -57,6 +61,19 @@ export interface CodeView {
57
61
adjustPosition ?: PositionAdjuster
58
62
/** Props for styling the buttons in the `CodeViewToolbar`. */
59
63
toolbarButtonProps ?: ButtonProps
64
+
65
+ isDiff ?: boolean
66
+
67
+ /** Gets the 1-indexed range of the code view */
68
+ getLineRanges : (
69
+ codeView : HTMLElement ,
70
+ part ?: DiffPart
71
+ ) => {
72
+ /** The first line shown in the code view. */
73
+ start : number
74
+ /** The last line shown in the code view. */
75
+ end : number
76
+ } [ ]
60
77
}
61
78
62
79
export type CodeViewWithOutSelector = Pick < CodeView , Exclude < keyof CodeView , 'selector' > >
@@ -71,6 +88,11 @@ interface OverlayPosition {
71
88
left : number
72
89
}
73
90
91
+ /**
92
+ * A function that gets the mount location for elements being mounted to the DOM.
93
+ */
94
+ export type MountGetter = ( ) => HTMLElement
95
+
74
96
/** Information for adding code intelligence to code views on arbitrary code hosts. */
75
97
export interface CodeHost {
76
98
/**
@@ -106,6 +128,13 @@ export interface CodeHost {
106
128
* Implementation of the search feature for a code host.
107
129
*/
108
130
search ?: SearchFeature
131
+
132
+ // Extensions related input
133
+
134
+ /**
135
+ * Get the DOM element where we'll mount the command palette for extensions.
136
+ */
137
+ getCommandPaletteMount ?: MountGetter
109
138
}
110
139
111
140
export interface FileInfo {
@@ -156,7 +185,19 @@ export interface FileInfo {
156
185
*
157
186
* @param codeHost
158
187
*/
159
- function initCodeIntelligence ( codeHost : CodeHost ) : { hoverifier : Hoverifier } {
188
+ function initCodeIntelligence (
189
+ codeHost : CodeHost ,
190
+ documents : BehaviorSubject < TextDocumentItem [ ] | null >
191
+ ) : {
192
+ hoverifier : Hoverifier
193
+ controllers : Partial < Controllers >
194
+ } {
195
+ const { extensionsContextController, extensionsController } : Partial < Controllers > =
196
+ useExtensions && codeHost . getCommandPaletteMount
197
+ ? initializeExtensions ( codeHost . getCommandPaletteMount , documents )
198
+ : { }
199
+ const simpleProviderFns = extensionsController ? createLSPFromExtensions ( extensionsController ) : lspViaAPIXlang
200
+
160
201
/** Emits when the go to definition button was clicked */
161
202
const goToDefinitionClicks = new Subject < MouseEvent > ( )
162
203
const nextGoToDefinitionClick = ( event : MouseEvent ) => goToDefinitionClicks . next ( event )
@@ -185,7 +226,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
185
226
186
227
const relativeElement = document . body
187
228
188
- const fetchJumpURL = createJumpURLFetcher ( lspViaAPIXlang . fetchDefinition , toPrettyBlobURL )
229
+ const fetchJumpURL = createJumpURLFetcher ( simpleProviderFns . fetchDefinition , toPrettyBlobURL )
189
230
190
231
const containerComponentUpdates = new Subject < void > ( )
191
232
@@ -202,7 +243,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
202
243
location . href = path
203
244
} ,
204
245
fetchHover : ( { line, character, part, ...rest } ) =>
205
- lspViaAPIXlang
246
+ simpleProviderFns
206
247
. fetchHover ( { ...rest , position : { line, character } } )
207
248
. pipe ( map ( hover => ( hover ? ( hover as HoverMerged ) : hover ) ) ) ,
208
249
fetchJumpURL,
@@ -260,7 +301,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
260
301
261
302
render ( < HoverOverlayContainer /> , overlayMount )
262
303
263
- return { hoverifier }
304
+ return { hoverifier, controllers : { extensionsContextController , extensionsController } }
264
305
}
265
306
266
307
/**
@@ -277,10 +318,19 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
277
318
initSearch ( codeHost . search )
278
319
}
279
320
280
- const { hoverifier } = initCodeIntelligence ( codeHost )
321
+ const documentsSubject = new BehaviorSubject < TextDocumentItem [ ] | null > ( [ ] )
322
+ const {
323
+ hoverifier,
324
+ controllers : { extensionsContextController, extensionsController } ,
325
+ } = initCodeIntelligence ( codeHost , documentsSubject )
281
326
282
327
const subscriptions = new Subscription ( )
283
328
329
+ subscriptions . add ( hoverifier )
330
+
331
+ // Keeps track of all documents on the page since calling this function (should be once per page).
332
+ let documents : TextDocumentItem [ ] = [ ]
333
+
284
334
subscriptions . add (
285
335
of ( document . body )
286
336
. pipe (
@@ -290,45 +340,119 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
290
340
) ,
291
341
observeOn ( animationFrameScheduler )
292
342
)
293
- . subscribe ( ( { codeView, info, dom, adjustPosition, getToolbarMount, toolbarButtonProps } ) => {
294
- const resolveContext : ContextResolver = ( { part } ) => ( {
295
- repoPath : part === 'base' ? info . baseRepoPath || info . repoPath : info . repoPath ,
296
- commitID : part === 'base' ? info . baseCommitID ! : info . commitID ,
297
- filePath : part === 'base' ? info . baseFilePath || info . filePath : info . filePath ,
298
- rev : part === 'base' ? info . baseRev || info . baseCommitID ! : info . rev || info . commitID ,
299
- } )
300
-
301
- subscriptions . add (
302
- hoverifier . hoverify ( {
303
- dom,
304
- positionEvents : of ( codeView ) . pipe ( findPositionsFromEvents ( dom ) ) ,
305
- resolveContext,
306
- adjustPosition,
307
- } )
308
- )
309
-
310
- codeView . classList . add ( 'sg-mounted' )
343
+ . subscribe (
344
+ ( {
345
+ codeView,
346
+ info,
347
+ isDiff,
348
+ getLineRanges,
349
+ dom,
350
+ adjustPosition,
351
+ getToolbarMount,
352
+ toolbarButtonProps,
353
+ } ) => {
354
+ const toURIWithPath = ( ctx : AbsoluteRepoFile ) =>
355
+ `git://${ ctx . repoPath } ?${ ctx . commitID } #${ ctx . filePath } `
356
+
357
+ if ( extensionsController ) {
358
+ const { content, baseContent } = getContentOfCodeView ( codeView , { isDiff, getLineRanges, dom } )
359
+
360
+ documents = [
361
+ // All the currently open documents
362
+ ...documents ,
363
+ // Either a normal file, or HEAD when codeView is a diff
364
+ {
365
+ uri : toURIWithPath ( info ) ,
366
+ languageId : getModeFromPath ( info . filePath ) || 'could not determine mode' ,
367
+ text : content ,
368
+ } ,
369
+ // When codeView is a diff, add BASE too
370
+ ...( baseContent && info . baseRepoPath && info . baseCommitID && info . baseFilePath
371
+ ? [
372
+ {
373
+ uri : toURIWithPath ( {
374
+ repoPath : info . baseRepoPath ,
375
+ commitID : info . baseCommitID ,
376
+ filePath : info . baseFilePath ,
377
+ } ) ,
378
+ languageId : getModeFromPath ( info . filePath ) || 'could not determine mode' ,
379
+ text : baseContent ,
380
+ } ,
381
+ ]
382
+ : [ ] ) ,
383
+ ]
384
+
385
+ if ( extensionsController && ! info . baseCommitID ) {
386
+ let oldDecorations : Disposable [ ] = [ ]
387
+
388
+ extensionsController . registries . textDocumentDecoration
389
+ . getDecorations ( toTextDocumentIdentifier ( info ) )
390
+ . subscribe ( decorations => {
391
+ for ( const old of oldDecorations ) {
392
+ old . dispose ( )
393
+ }
394
+ oldDecorations = [ ]
395
+ for ( const decoration of decorations || [ ] ) {
396
+ try {
397
+ oldDecorations . push (
398
+ applyDecoration ( dom , {
399
+ codeView,
400
+ decoration,
401
+ } )
402
+ )
403
+ } catch ( e ) {
404
+ console . warn ( e )
405
+ }
406
+ }
407
+ } )
408
+ }
311
409
312
- if ( ! getToolbarMount ) {
313
- return
314
- }
410
+ documentsSubject . next ( documents )
411
+ }
315
412
316
- const mount = getToolbarMount ( codeView )
413
+ const resolveContext : ContextResolver = ( { part } ) => ( {
414
+ repoPath : part === 'base' ? info . baseRepoPath || info . repoPath : info . repoPath ,
415
+ commitID : part === 'base' ? info . baseCommitID ! : info . commitID ,
416
+ filePath : part === 'base' ? info . baseFilePath || info . filePath : info . filePath ,
417
+ rev : part === 'base' ? info . baseRev || info . baseCommitID ! : info . rev || info . commitID ,
418
+ } )
317
419
318
- render (
319
- < CodeViewToolbar
320
- { ...info }
321
- buttonProps = {
322
- toolbarButtonProps || {
323
- className : '' ,
324
- style : { } ,
420
+ subscriptions . add (
421
+ hoverifier . hoverify ( {
422
+ dom,
423
+ positionEvents : of ( codeView ) . pipe ( findPositionsFromEvents ( dom ) ) ,
424
+ resolveContext,
425
+ adjustPosition,
426
+ } )
427
+ )
428
+
429
+ codeView . classList . add ( 'sg-mounted' )
430
+
431
+ if ( ! getToolbarMount ) {
432
+ return
433
+ }
434
+
435
+ const mount = getToolbarMount ( codeView )
436
+
437
+ render (
438
+ < CodeViewToolbar
439
+ { ...info }
440
+ extensions = { extensionsContextController }
441
+ extensionsController = { extensionsController }
442
+ buttonProps = {
443
+ toolbarButtonProps || {
444
+ className : '' ,
445
+ style : { } ,
446
+ }
325
447
}
326
- }
327
- simpleProviderFns = { lspViaAPIXlang }
328
- /> ,
329
- mount
330
- )
331
- } )
448
+ simpleProviderFns = {
449
+ extensionsController ? createLSPFromExtensions ( extensionsController ) : lspViaAPIXlang
450
+ }
451
+ /> ,
452
+ mount
453
+ )
454
+ }
455
+ )
332
456
)
333
457
334
458
return subscriptions
0 commit comments