Skip to content
This repository was archived by the owner on Nov 25, 2021. It is now read-only.

Commit 1425283

Browse files
committed
feat: support using provided func to generate references URL
BREAKING CHANGE: You must provide a getReferencesURL function.
1 parent 24ac0fb commit 1425283

File tree

8 files changed

+116
-207
lines changed

8 files changed

+116
-207
lines changed

src/HoverOverlay.tsx

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ import * as React from 'react'
77
import { MarkedString, MarkupContent, MarkupKind } from 'vscode-languageserver-types'
88
import { asError, ErrorLike, isErrorLike } from './errors'
99
import { highlightCodeSafe, renderMarkdown, toNativeEvent } from './helpers'
10-
import { HoveredTokenContext } from './hoverifier'
1110
import { HoveredToken } from './token_position'
1211
import { HoverMerged, LOADING } from './types'
13-
import { toPrettyBlobURL } from './url'
1412

1513
/** The component used to render a link */
1614
export type LinkComponent = React.ComponentType<{ to: string } & React.HTMLAttributes<HTMLElement>>
@@ -31,7 +29,10 @@ const ButtonOrLink: React.StatelessComponent<
3129
)
3230
}
3331

34-
export interface HoverOverlayProps {
32+
/**
33+
* @template C Extra context for the hovered token.
34+
*/
35+
export interface HoverOverlayProps<C = {}> {
3536
/** What to show as contents */
3637
hoverOrError?: typeof LOADING | HoverMerged | null | ErrorLike // TODO disallow null and undefined
3738

@@ -44,6 +45,13 @@ export interface HoverOverlayProps {
4445
*/
4546
definitionURLOrError?: typeof LOADING | { jumpURL: string } | null | ErrorLike
4647

48+
/**
49+
* The URL to navigate to to view references.
50+
* If loaded, is set as the href of the find-references button.
51+
* If undefined or null, the button is disabled.
52+
*/
53+
referencesURL?: string | null
54+
4755
/** The position of the tooltip (assigned to `style`) */
4856
overlayPosition?: { left: number; top: number }
4957

@@ -54,7 +62,7 @@ export interface HoverOverlayProps {
5462
* The hovered token (position and word).
5563
* Used for the Find References buttons and for error messages
5664
*/
57-
hoveredToken?: HoveredToken & HoveredTokenContext
65+
hoveredToken?: HoveredToken & C
5866

5967
/** Whether to show the close button for the hover overlay */
6068
showCloseButton: boolean
@@ -81,8 +89,9 @@ export const isJumpURL = (val: any): val is { jumpURL: string } =>
8189
const transformMouseEvent = (handler: (event: MouseEvent) => void) => (event: React.MouseEvent<HTMLElement>) =>
8290
handler(toNativeEvent(event))
8391

84-
export const HoverOverlay: React.StatelessComponent<HoverOverlayProps> = ({
92+
export const HoverOverlay: <C>(props: HoverOverlayProps<C>) => React.ReactElement<any> = ({
8593
definitionURLOrError,
94+
referencesURL,
8695
hoveredToken,
8796
hoverOrError,
8897
hoverRef,
@@ -187,24 +196,18 @@ export const HoverOverlay: React.StatelessComponent<HoverOverlayProps> = ({
187196
>
188197
Go to definition {definitionURLOrError === LOADING && <LoadingSpinner className="icon-inline" />}
189198
</ButtonOrLink>
190-
<ButtonOrLink
191-
linkComponent={linkComponent}
192-
// tslint:disable-next-line:jsx-no-lambda
193-
onClick={() => logTelemetryEvent('FindRefsClicked')}
194-
to={
195-
hoveredToken &&
196-
toPrettyBlobURL({
197-
repoPath: hoveredToken.repoPath,
198-
rev: hoveredToken.rev,
199-
filePath: hoveredToken.filePath,
200-
position: hoveredToken,
201-
viewState: 'references',
202-
})
203-
}
204-
className="btn btn-secondary hover-overlay__action e2e-tooltip-find-refs"
205-
>
206-
Find references
207-
</ButtonOrLink>
199+
{hoveredToken &&
200+
referencesURL && (
201+
<ButtonOrLink
202+
linkComponent={linkComponent}
203+
// tslint:disable-next-line:jsx-no-lambda
204+
onClick={() => logTelemetryEvent('FindRefsClicked')}
205+
to={referencesURL}
206+
className="btn btn-secondary hover-overlay__action e2e-tooltip-find-refs"
207+
>
208+
Find references
209+
</ButtonOrLink>
210+
)}
208211
</div>
209212
{definitionURLOrError === null ? (
210213
<div className="alert alert-info hover-overlay__alert-below">

src/hoverifier.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ describe('Hoverifier', () => {
134134
const fetchHover = createStubHoverFetcher({})
135135
const fetchJumpURL = createStubJumpURLFetcher('def')
136136

137-
const adjustPosition: PositionAdjuster = ({ direction, position }) => {
137+
const adjustPosition: PositionAdjuster<{}> = ({ direction, position }) => {
138138
adjustmentDirections.next(direction)
139139

140140
return of(position)

src/hoverifier.ts

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ import {
4343
getTokenAtPosition,
4444
HoveredToken,
4545
} from './token_position'
46-
import { EMODENOTFOUND, HoverMerged, LOADING } from './types'
47-
import { FileSpec, LineOrPositionOrRange, RepoSpec, ResolvedRevSpec, RevSpec } from './url'
46+
import { EMODENOTFOUND, HoverMerged, LineOrPositionOrRange, LOADING } from './types'
4847

4948
export { HoveredToken }
5049

51-
export interface HoverifierOptions {
50+
/**
51+
* @template C Extra context for the hovered token.
52+
*/
53+
export interface HoverifierOptions<C extends object> {
5254
/**
5355
* Emit the HoverOverlay element on this after it was rerendered when its content changed and it needs to be repositioned.
5456
*/
@@ -86,17 +88,20 @@ export interface HoverifierOptions {
8688
*/
8789
logTelemetryEvent?: (event: string, data?: any) => void
8890

89-
fetchHover: HoverFetcher
90-
fetchJumpURL: JumpURLFetcher
91+
fetchHover: HoverFetcher<C>
92+
fetchJumpURL: JumpURLFetcher<C>
93+
getReferencesURL: (hoverToken: HoveredToken & C) => string | null
9194
}
9295

9396
/**
9497
* A Hoverifier is a function that hoverifies one code view element in the DOM.
9598
* It will do very dirty things to it. Only call it if you're into that.
9699
*
97100
* There can be multiple code views in the DOM, which will only show a single HoverOverlay if the same Hoverifier was used.
101+
*
102+
* @template C Extra context for the hovered token.
98103
*/
99-
export interface Hoverifier {
104+
export interface Hoverifier<C extends object> {
100105
/**
101106
* The current Hover state. You can use this to read the initial state synchronously.
102107
*/
@@ -109,7 +114,7 @@ export interface Hoverifier {
109114
/**
110115
* Hoverifies a code view.
111116
*/
112-
hoverify(options: HoverifyOptions): Subscription
117+
hoverify(options: HoverifyOptions<C>): Subscription
113118

114119
unsubscribe(): void
115120
}
@@ -139,31 +144,42 @@ export enum AdjustmentDirection {
139144
ActualToCodeView,
140145
}
141146

142-
export interface AdjustPositionProps {
147+
/**
148+
* @template C Extra context for the hovered token.
149+
*/
150+
export interface AdjustPositionProps<C extends object> {
143151
/** The code view the token is in. */
144152
codeView: HTMLElement
145153
/** The position the token is at. */
146-
position: HoveredToken & HoveredTokenContext
154+
position: HoveredToken & C
147155
/** The direction the adjustment should go. */
148156
direction: AdjustmentDirection
149157
}
150158

151159
/**
152160
* Function to adjust positions coming into and out of hoverifier. It can be used to correct the position used in HoverFetcher and
153161
* JumpURLFetcher requests and the position of th etoken to highlight in the code view. This is useful for code hosts that convert whitespace.
162+
*
163+
*
164+
* @template C Extra context for the hovered token.
154165
*/
155-
export type PositionAdjuster = (props: AdjustPositionProps) => SubscribableOrPromise<Position>
166+
export type PositionAdjuster<C extends object> = (props: AdjustPositionProps<C>) => SubscribableOrPromise<Position>
156167

157168
/**
158169
* HoverifyOptions that need to be included internally with every event
170+
*
171+
* @template C Extra context for the hovered token.
159172
*/
160-
export interface EventOptions {
161-
resolveContext: ContextResolver
162-
adjustPosition?: PositionAdjuster
173+
export interface EventOptions<C extends object> {
174+
resolveContext: ContextResolver<C>
175+
adjustPosition?: PositionAdjuster<C>
163176
dom: DOMFunctions
164177
}
165178

166-
export interface HoverifyOptions extends EventOptions {
179+
/**
180+
* @template C Extra context for the hovered token.
181+
*/
182+
export interface HoverifyOptions<C extends object> extends EventOptions<C> {
167183
positionEvents: Subscribable<PositionEvent>
168184

169185
/**
@@ -190,9 +206,13 @@ export interface HoverState {
190206
selectedPosition?: LineOrPositionOrRange
191207
}
192208

193-
interface InternalHoverifierState {
209+
/**
210+
* @template C Extra context for the hovered token.
211+
*/
212+
interface InternalHoverifierState<C extends object> {
194213
hoverOrError?: typeof LOADING | HoverMerged | null | ErrorLike
195214
definitionURLOrError?: typeof LOADING | { jumpURL: string } | null | ErrorLike
215+
referencesURL?: string | null
196216

197217
hoverOverlayIsFixed: boolean
198218

@@ -206,7 +226,7 @@ interface InternalHoverifierState {
206226
clickedGoToDefinition: false | 'same-tab' | 'new-tab'
207227

208228
/** The currently hovered token */
209-
hoveredToken?: HoveredToken & HoveredTokenContext
229+
hoveredToken?: HoveredToken & C
210230

211231
mouseIsMoving: boolean
212232

@@ -221,13 +241,13 @@ interface InternalHoverifierState {
221241
/**
222242
* Returns true if the HoverOverlay component should be rendered according to the given state.
223243
*/
224-
const shouldRenderOverlay = (state: InternalHoverifierState): boolean =>
244+
const shouldRenderOverlay = (state: InternalHoverifierState<{}>): boolean =>
225245
!(!state.hoverOverlayIsFixed && state.mouseIsMoving) && overlayUIHasContent(state)
226246

227247
/**
228248
* Maps internal HoverifierState to the publicly exposed HoverState
229249
*/
230-
const internalToExternalState = (internalState: InternalHoverifierState): HoverState => ({
250+
const internalToExternalState = (internalState: InternalHoverifierState<{}>): HoverState => ({
231251
selectedPosition: internalState.selectedPosition,
232252
hoverOverlayProps: shouldRenderOverlay(internalState)
233253
? {
@@ -238,6 +258,7 @@ const internalToExternalState = (internalState: InternalHoverifierState): HoverS
238258
isJumpURL(internalState.definitionURLOrError) || internalState.clickedGoToDefinition
239259
? internalState.definitionURLOrError
240260
: undefined,
261+
referencesURL: internalState.referencesURL,
241262
hoveredToken: internalState.hoveredToken,
242263
showCloseButton: internalState.hoverOverlayIsFixed,
243264
}
@@ -250,40 +271,52 @@ export const LOADER_DELAY = 300
250271
/** The time in ms after the mouse has stopped moving in which to show the tooltip */
251272
export const TOOLTIP_DISPLAY_DELAY = 100
252273

253-
export type HoverFetcher = (position: HoveredToken & HoveredTokenContext) => SubscribableOrPromise<HoverMerged | null>
254-
export type JumpURLFetcher = (position: HoveredToken & HoveredTokenContext) => SubscribableOrPromise<string | null>
274+
/**
275+
* @template C Extra context for the hovered token.
276+
*/
277+
export type HoverFetcher<C extends object> = (position: HoveredToken & C) => SubscribableOrPromise<HoverMerged | null>
278+
279+
/**
280+
* @template C Extra context for the hovered token.
281+
*/
282+
export type JumpURLFetcher<C extends object> = (position: HoveredToken & C) => SubscribableOrPromise<string | null>
255283

256284
/**
257285
* Function responsible for resolving the position of a hovered token
258286
* and its diff part to a full context including repository, commit ID and file path.
287+
*
288+
* @template C Extra context for the hovered token.
259289
*/
260-
export type ContextResolver = (hoveredToken: HoveredToken) => HoveredTokenContext
290+
export type ContextResolver<C extends object> = (hoveredToken: HoveredToken) => C
261291

262-
export interface HoveredTokenContext extends RepoSpec, RevSpec, FileSpec, ResolvedRevSpec {}
263-
264-
export const createHoverifier = ({
292+
/**
293+
* @template C Extra context for the hovered token.
294+
*/
295+
export function createHoverifier<C extends object>({
265296
goToDefinitionClicks,
266297
closeButtonClicks,
267298
hoverOverlayRerenders,
268299
pushHistory,
269300
fetchHover,
270301
fetchJumpURL,
302+
getReferencesURL,
271303
logTelemetryEvent = noop,
272-
}: HoverifierOptions): Hoverifier => {
304+
}: HoverifierOptions<C>): Hoverifier<C> {
273305
// Internal state that is not exposed to the caller
274306
// Shared between all hoverified code views
275-
const container = createObservableStateContainer<InternalHoverifierState>({
307+
const container = createObservableStateContainer<InternalHoverifierState<C>>({
276308
hoverOverlayIsFixed: false,
277309
clickedGoToDefinition: false,
278310
definitionURLOrError: undefined,
311+
referencesURL: undefined,
279312
hoveredToken: undefined,
280313
hoverOrError: undefined,
281314
hoverOverlayPosition: undefined,
282315
mouseIsMoving: false,
283316
selectedPosition: undefined,
284317
})
285318

286-
interface MouseEventTrigger extends PositionEvent, EventOptions {}
319+
interface MouseEventTrigger extends PositionEvent, EventOptions<C> {}
287320

288321
// These Subjects aggregate all events from all hoverified code views
289322
const allPositionsFromEvents = new Subject<MouseEventTrigger>()
@@ -295,7 +328,7 @@ export const createHoverifier = ({
295328
const allCodeMouseOvers = allPositionsFromEvents.pipe(filter(isEventType('mouseover')))
296329
const allCodeClicks = allPositionsFromEvents.pipe(filter(isEventType('click')))
297330

298-
const allPositionJumps = new Subject<PositionJump & EventOptions>()
331+
const allPositionJumps = new Subject<PositionJump & EventOptions<C>>()
299332

300333
const subscription = new Subscription()
301334

@@ -489,6 +522,13 @@ export const createHoverifier = ({
489522
share()
490523
)
491524

525+
/** Emits new referencesURL values. */
526+
subscription.add(
527+
resolvedPositions
528+
.pipe(map(({ position }) => (position ? getReferencesURL(position) : null)))
529+
.subscribe(referencesURL => container.update({ referencesURL }))
530+
)
531+
492532
/**
493533
* For every position, emits an Observable with new values for the `hoverOrError` state.
494534
* This is a higher-order Observable (Observable that emits Observables).
@@ -498,10 +538,10 @@ export const createHoverifier = ({
498538
eventType: SupportedMouseEvent | 'jump'
499539
dom: DOMFunctions
500540
target: HTMLElement
501-
adjustPosition?: PositionAdjuster
541+
adjustPosition?: PositionAdjuster<C>
502542
codeView: HTMLElement
503543
hoverOrError?: typeof LOADING | HoverMerged | Error | null
504-
position?: HoveredToken & HoveredTokenContext
544+
position?: HoveredToken & C
505545
part?: DiffPart
506546
}>
507547
> = resolvedPositions.pipe(
@@ -611,7 +651,7 @@ export const createHoverifier = ({
611651
zip(resolvedPositions, hoverObservables)
612652
.pipe(
613653
distinctUntilChanged(([positionA], [positionB]) => isEqual(positionA, positionB)),
614-
switchMap(([position, hoverObservable]) => hoverObservable),
654+
switchMap(([, hoverObservable]) => hoverObservable),
615655
filter(({ hoverOrError }) => HoverMerged.is(hoverOrError))
616656
)
617657
.subscribe(() => {
@@ -768,7 +808,7 @@ export const createHoverifier = ({
768808
map(internalToExternalState),
769809
distinctUntilChanged((a, b) => isEqual(a, b))
770810
),
771-
hoverify({ positionEvents, positionJumps = EMPTY, ...eventOptions }: HoverifyOptions): Subscription {
811+
hoverify({ positionEvents, positionJumps = EMPTY, ...eventOptions }: HoverifyOptions<C>): Subscription {
772812
const subscription = new Subscription()
773813
const eventWithOptions = map((event: PositionEvent) => ({ ...event, ...eventOptions }))
774814
// Broadcast all events from this code view

src/overlay_position.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('overlay_position', () => {
4747
position: absolute;
4848
}
4949
`
50-
document.head.appendChild(style)
50+
document.head!.appendChild(style)
5151

5252
relativeElement = document.createElement('div')
5353
relativeElement.className = 'relative-element'

src/testutils/lsp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const createHoverMerged = (hover: Partial<HoverMerged> = {}): HoverMerged
2525
* @param hover optional values to be passed to createHoverMerged
2626
* @param delayTime optionally delay the hover fetch
2727
*/
28-
export function createStubHoverFetcher(hover: Partial<HoverMerged> = {}, delayTime?: number): HoverFetcher {
28+
export function createStubHoverFetcher(hover: Partial<HoverMerged> = {}, delayTime?: number): HoverFetcher<{}> {
2929
return () => of(createHoverMerged(hover)).pipe(delay(delayTime || 0))
3030
}
3131

@@ -34,6 +34,6 @@ export function createStubHoverFetcher(hover: Partial<HoverMerged> = {}, delayTi
3434
* @param jumpURL optional value to emit as the url
3535
* @param delayTime optionally delay the jump url fetch
3636
*/
37-
export function createStubJumpURLFetcher(jumpURL = '', delayTime?: number): JumpURLFetcher {
37+
export function createStubJumpURLFetcher(jumpURL = '', delayTime?: number): JumpURLFetcher<{}> {
3838
return () => of(jumpURL).pipe(delay(delayTime || 0))
3939
}

0 commit comments

Comments
 (0)