@@ -15,24 +15,78 @@ 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 { Observable , of , Subject , Subscription } from 'rxjs'
19- import { filter , map , withLatestFrom } from 'rxjs/operators'
18+ import { animationFrameScheduler , Observable , of , Subject , Subscription } from 'rxjs'
19+ import { filter , map , mergeMap , observeOn , withLatestFrom } from 'rxjs/operators'
2020
2121import { createJumpURLFetcher } from '../../shared/backend/lsp'
2222import { lspViaAPIXlang } from '../../shared/backend/lsp'
2323import { ButtonProps , CodeViewToolbar } from '../../shared/components/CodeViewToolbar'
2424import { eventLogger , sourcegraphUrl } from '../../shared/util/context'
25+ import { githubCodeHost } from '../github/code_intelligence'
26+ import { phabricatorCodeHost } from '../phabricator/code_intelligence'
27+ import { findCodeViews } from './code_views'
28+
29+ /**
30+ * Defines a type of code view a given code host can have. It tells us how to
31+ * look for the code view and how to do certain things when we find it.
32+ */
33+ export interface CodeView {
34+ /** A selector used by `document.querySelectorAll` to find the code view. */
35+ selector : string
36+ /** The DOMFunctions for the code view. */
37+ dom : DOMFunctions
38+ /**
39+ * Finds or creates a DOM element where we should inject the
40+ * `CodeViewToolbar`. This function is responsible for ensuring duplicate
41+ * mounts aren't created.
42+ */
43+ getToolbarMount ?: ( codeView : HTMLElement , part ?: DiffPart ) => HTMLElement
44+ /**
45+ * Resolves the file info for a given code view. It returns an observable
46+ * because some code hosts need to resolve this asynchronously. The
47+ * observable should only emit once.
48+ */
49+ resolveFileInfo : ( codeView : HTMLElement ) => Observable < FileInfo >
50+ /**
51+ * In some situations, we need to be able to adjust the position going into
52+ * and coming out of codeintellify. For example, Phabricator converts tabs
53+ * to spaces in it's DOM.
54+ */
55+ adjustPosition ?: PositionAdjuster
56+ /** Props for styling the buttons in the `CodeViewToolbar`. */
57+ toolbarButtonProps ?: ButtonProps
58+ }
59+
60+ export type CodeViewWithOutSelector = Pick < CodeView , Exclude < keyof CodeView , 'selector' > >
61+
62+ export interface CodeViewResolver {
63+ selector : string
64+ resolveCodeView : ( elem : HTMLElement ) => CodeViewWithOutSelector
65+ }
2566
2667/** Information for adding code intelligence to code views on arbitrary code hosts. */
2768export interface CodeHost {
2869 /**
2970 * The name of the code host. This will be added as a className to the overlay mount.
3071 */
3172 name : string
73+
3274 /**
3375 * The list of types of code views to try to annotate.
3476 */
35- codeViews : CodeView [ ]
77+ codeViews ?: CodeView [ ]
78+
79+ /**
80+ * Resolve `CodeView`s from the DOM. This is useful when each code view type
81+ * doesn't have a distinct selector for
82+ */
83+ codeViewResolver ?: CodeViewResolver
84+
85+ /**
86+ * Checks to see if the current context the code is running in is within
87+ * the given code host.
88+ */
89+ check : ( ) => Promise < boolean > | boolean
3690}
3791
3892export interface FileInfo {
@@ -55,7 +109,6 @@ export interface FileInfo {
55109 * The revision the code view is at. If a `baseRev` is provided, this value is treated as the head rev.
56110 */
57111 rev ?: string
58-
59112 /**
60113 * The repo bath for the BASE side of a diff. This is useful for Phabricator
61114 * staging areas since they are separate repos.
@@ -78,31 +131,6 @@ export interface FileInfo {
78131 baseHasFileContents ?: boolean
79132}
80133
81- /**
82- * Defines a type of code view a given code host can have. It tells us how to
83- * look for the code view and how to do certain things when we find it.
84- */
85- export interface CodeView {
86- /** A selector used by `document.querySelectorAll` to find the code view. */
87- selector : string
88- /** The DOMFunctions for the code view. */
89- dom : DOMFunctions
90- /** Finds or creates a DOM element where we should inject the `CodeViewToolbar`. */
91- getToolbarMount ?: ( codeView : HTMLElement , part ?: DiffPart ) => HTMLElement
92- /** Resolves the file info for a given code view. It returns an observable
93- * because some code hosts need to resolve this asynchronously. The
94- * observable should only emit once.
95- */
96- resolveFileInfo : ( codeView : HTMLElement ) => Observable < FileInfo >
97- /** In some situations, we need to be able to adjust the position going into
98- * and coming out of codeintellify. For example, Phabricator converts tabs
99- * to spaces in it's DOM.
100- */
101- adjustPosition ?: PositionAdjuster
102- /** Props for styling the buttons in the `CodeViewToolbar`. */
103- toolbarButtonProps ?: ButtonProps
104- }
105-
106134/**
107135 * Prepares the page for code intelligence. It creates the hoverifier, injects
108136 * and mounts the hover overlay and then returns the hoverifier.
@@ -196,41 +224,43 @@ function initCodeIntelligence(codeHost: CodeHost): { hoverifier: Hoverifier } {
196224 * ResolvedCodeView attaches an actual code view DOM element that was found on
197225 * the page to the CodeView type being passed around by this file.
198226 */
199- export interface ResolvedCodeView extends CodeView {
227+ export interface ResolvedCodeView extends CodeViewWithOutSelector {
200228 /** The code view DOM element. */
201229 codeView : HTMLElement
202230}
203231
204- function findCodeViews ( codeViewInfos : CodeView [ ] ) : Observable < ResolvedCodeView > {
205- return new Observable < ResolvedCodeView > ( observer => {
206- for ( const info of codeViewInfos ) {
207- const elements = document . querySelectorAll < HTMLElement > ( info . selector )
208- for ( const codeView of elements ) {
209- observer . next ( { ...info , codeView } )
210- }
211- }
212- } )
213- }
232+ function handleCodeHost ( codeHost : CodeHost ) : Subscription {
233+ const { hoverifier } = initCodeIntelligence ( codeHost )
214234
215- export function injectCodeIntelligence ( codeHostInfo : CodeHost ) : Subscription {
216- const { hoverifier } = initCodeIntelligence ( codeHostInfo )
235+ const subscriptions = new Subscription ( )
217236
218- return findCodeViews ( codeHostInfo . codeViews ) . subscribe (
219- ( { codeView, dom, resolveFileInfo, adjustPosition, getToolbarMount, toolbarButtonProps } ) =>
220- resolveFileInfo ( codeView ) . subscribe ( info => {
237+ subscriptions . add (
238+ of ( document . body )
239+ . pipe (
240+ findCodeViews ( codeHost ) ,
241+ mergeMap ( ( { codeView, resolveFileInfo, ...rest } ) =>
242+ resolveFileInfo ( codeView ) . pipe ( map ( info => ( { info, codeView, ...rest } ) ) )
243+ ) ,
244+ observeOn ( animationFrameScheduler )
245+ )
246+ . subscribe ( ( { codeView, info, dom, adjustPosition, getToolbarMount, toolbarButtonProps } ) => {
221247 const resolveContext : ContextResolver = ( { part } ) => ( {
222248 repoPath : part === 'base' ? info . baseRepoPath || info . repoPath : info . repoPath ,
223249 commitID : part === 'base' ? info . baseCommitID ! : info . commitID ,
224250 filePath : part === 'base' ? info . baseFilePath ! : info . filePath ,
225251 rev : part === 'base' ? info . baseRev || info . baseCommitID ! : info . rev || info . commitID ,
226252 } )
227253
228- hoverifier . hoverify ( {
229- dom,
230- positionEvents : of ( codeView ) . pipe ( findPositionsFromEvents ( dom ) ) ,
231- resolveContext,
232- adjustPosition,
233- } )
254+ subscriptions . add (
255+ hoverifier . hoverify ( {
256+ dom,
257+ positionEvents : of ( codeView ) . pipe ( findPositionsFromEvents ( dom ) ) ,
258+ resolveContext,
259+ adjustPosition,
260+ } )
261+ )
262+
263+ codeView . classList . add ( 'sg-mounted' )
234264
235265 if ( ! getToolbarMount ) {
236266 return
@@ -253,4 +283,32 @@ export function injectCodeIntelligence(codeHostInfo: CodeHost): Subscription {
253283 )
254284 } )
255285 )
286+
287+ return subscriptions
288+ }
289+
290+ async function injectCodeIntelligenceToCodeHosts ( codeHosts : CodeHost [ ] ) : Promise < Subscription > {
291+ const subscriptions = new Subscription ( )
292+
293+ for ( const codeHost of codeHosts ) {
294+ const isCodeHost = await Promise . resolve ( codeHost . check ( ) )
295+ if ( isCodeHost ) {
296+ subscriptions . add ( handleCodeHost ( codeHost ) )
297+ }
298+ }
299+
300+ return subscriptions
301+ }
302+
303+ /**
304+ * Injects all code hosts into the page.
305+ *
306+ * @returns A promise with a subscription containing all subscriptions for code
307+ * intelligence. Unsubscribing will clean up subscriptions for hoverify and any
308+ * incomplete setup requests.
309+ */
310+ export async function injectCodeIntelligence ( ) : Promise < Subscription > {
311+ const codeHosts : CodeHost [ ] = [ githubCodeHost , phabricatorCodeHost ]
312+
313+ return await injectCodeIntelligenceToCodeHosts ( codeHosts )
256314}
0 commit comments