@@ -15,17 +15,21 @@ import { HoverMerged } from '@sourcegraph/codeintellify/lib/types'
1515import { toPrettyBlobURL } from '@sourcegraph/codeintellify/lib/url'
1616import * as React from 'react'
1717import { render } from 'react-dom'
18- import { animationFrameScheduler , Observable , of , Subject , Subscription } from 'rxjs'
18+ import { animationFrameScheduler , BehaviorSubject , Observable , of , Subject , Subscription } from 'rxjs'
1919import { filter , map , mergeMap , observeOn , withLatestFrom } from 'rxjs/operators'
2020
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'
2325import { 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'
2528import { githubCodeHost } from '../github/code_intelligence'
2629import { gitlabCodeHost } from '../gitlab/code_intelligence'
2730import { 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'
2933import { initSearch , SearchFeature } from './search'
3034
3135/**
@@ -57,6 +61,19 @@ export interface CodeView {
5761 adjustPosition ?: PositionAdjuster
5862 /** Props for styling the buttons in the `CodeViewToolbar`. */
5963 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+ } [ ]
6077}
6178
6279export type CodeViewWithOutSelector = Pick < CodeView , Exclude < keyof CodeView , 'selector' > >
@@ -71,6 +88,11 @@ interface OverlayPosition {
7188 left : number
7289}
7390
91+ /**
92+ * A function that gets the mount location for elements being mounted to the DOM.
93+ */
94+ export type MountGetter = ( ) => HTMLElement
95+
7496/** Information for adding code intelligence to code views on arbitrary code hosts. */
7597export interface CodeHost {
7698 /**
@@ -106,6 +128,13 @@ export interface CodeHost {
106128 * Implementation of the search feature for a code host.
107129 */
108130 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
109138}
110139
111140export interface FileInfo {
@@ -156,7 +185,19 @@ export interface FileInfo {
156185 *
157186 * @param codeHost
158187 */
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+
160201 /** Emits when the go to definition button was clicked */
161202 const goToDefinitionClicks = new Subject < MouseEvent > ( )
162203 const nextGoToDefinitionClick = ( event : MouseEvent ) => goToDefinitionClicks . next ( event )
@@ -185,7 +226,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
185226
186227 const relativeElement = document . body
187228
188- const fetchJumpURL = createJumpURLFetcher ( lspViaAPIXlang . fetchDefinition , toPrettyBlobURL )
229+ const fetchJumpURL = createJumpURLFetcher ( simpleProviderFns . fetchDefinition , toPrettyBlobURL )
189230
190231 const containerComponentUpdates = new Subject < void > ( )
191232
@@ -202,7 +243,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
202243 location . href = path
203244 } ,
204245 fetchHover : ( { line, character, part, ...rest } ) =>
205- lspViaAPIXlang
246+ simpleProviderFns
206247 . fetchHover ( { ...rest , position : { line, character } } )
207248 . pipe ( map ( hover => ( hover ? ( hover as HoverMerged ) : hover ) ) ) ,
208249 fetchJumpURL,
@@ -260,7 +301,7 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
260301
261302 render ( < HoverOverlayContainer /> , overlayMount )
262303
263- return { hoverifier }
304+ return { hoverifier, controllers : { extensionsContextController , extensionsController } }
264305}
265306
266307/**
@@ -277,10 +318,19 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
277318 initSearch ( codeHost . search )
278319 }
279320
280- const { hoverifier } = initCodeIntelligence ( codeHost )
321+ const documentsSubject = new BehaviorSubject < TextDocumentItem [ ] | null > ( [ ] )
322+ const {
323+ hoverifier,
324+ controllers : { extensionsContextController, extensionsController } ,
325+ } = initCodeIntelligence ( codeHost , documentsSubject )
281326
282327 const subscriptions = new Subscription ( )
283328
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+
284334 subscriptions . add (
285335 of ( document . body )
286336 . pipe (
@@ -290,45 +340,119 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
290340 ) ,
291341 observeOn ( animationFrameScheduler )
292342 )
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+ }
311409
312- if ( ! getToolbarMount ) {
313- return
314- }
410+ documentsSubject . next ( documents )
411+ }
315412
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+ } )
317419
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+ }
325447 }
326- }
327- simpleProviderFns = { lspViaAPIXlang }
328- /> ,
329- mount
330- )
331- } )
448+ simpleProviderFns = {
449+ extensionsController ? createLSPFromExtensions ( extensionsController ) : lspViaAPIXlang
450+ }
451+ /> ,
452+ mount
453+ )
454+ }
455+ )
332456 )
333457
334458 return subscriptions
0 commit comments