This repository has been archived by the owner on Mar 18, 2024. It is now read-only.
/
activate.ts
360 lines (332 loc) · 11.4 KB
/
activate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
import { BehaviorSubject, from, Observable, Subject } from 'rxjs'
import { distinctUntilChanged, map } from 'rxjs/operators'
import * as sourcegraph from 'sourcegraph'
import { LanguageSpec } from './language-specs/spec'
import { Logger, RedactingLogger } from './logging'
import { getOrCreateAccessToken } from './lsp/auth'
import { LSPClient } from './lsp/client'
import { FeatureOptions } from './lsp/registration'
import {
createProviderWrapper,
ProviderWrapper,
ReferencesProvider,
} from './providers'
/**
* A factory function that attempts to create an LSP client and register
* providers with the given extension context. This function returns true
* if providers are registered and false otherwise.
*/
export type LSPFactory = (
ctx: sourcegraph.ExtensionContext,
providerWrapper: ProviderWrapper
) => Promise<boolean>
/**
* A factory function that creates an LSP client. This function returns the
* client and the a features option subject that can be passed feature
* options at runtime to change the behavior of the registered providers.
*
* @param args Parameter bag.
*/
export type ClientFactory<S> = (args: {
/** The extension context. */
ctx: sourcegraph.ExtensionContext
/** The URL of the LSP server. */
serverURL: string
/** The URL of the Sourcegraph API. */
sourcegraphURL: URL
/** The access token. */
accessToken?: string
/**
* A value that can decorate definition, references, and hover providers
* with LSIF and basic intelligence.
*/
providerWrapper: ProviderWrapper
/** The current settings. */
settings: S
}) => Promise<{
client: LSPClient
featureOptionsSubject: Subject<FeatureOptions>
}>
/**
* A factory function that creates an LSP-based external references provider.
*
* @param args Parameter bag.
*/
export type ExternalReferencesProviderFactory<S> = (args: {
/** The LSP client. */
client: LSPClient
/** The current settings. */
settings: S
/** A URL of the Sourcegraph API reachable from the language server. */
sourcegraphServerURL: URL
/** A URL of the Sourcegraph API reachable from the browser. */
sourcegraphClientURL: URL
/** The access token. */
accessToken?: string
}) => ReferencesProvider
/**
* A dummy context that is used for versions of Sourcegraph to 3.0.
*/
const DUMMY_CTX = {
subscriptions: {
add: (): void => {
/* no-op */
},
},
}
/**
* Activate the extension. This will provide the LSP factory with a provider wrapper that
* decorates LSP providers with LSIF-based code intelligence. If no LSP factory is provided,
* or if it returns false indicating that it cannot register an LSP client, then the default
* LSIF and search-based providers are registered.
*
* @param ctx The extension context.
* @param selector The document selector for which this extension is active.
* @param languageSpec The language spec used to provide search-based code intelligence.
* @param lspFactory An optional factory that registers an LSP client.
* @param logger An optional logger instance.
*/
export async function activateCodeIntel(
ctx: sourcegraph.ExtensionContext = DUMMY_CTX,
selector: sourcegraph.DocumentSelector,
languageSpec: LanguageSpec,
lspFactory?: LSPFactory,
logger: Logger = new RedactingLogger(console)
): Promise<void> {
const wrapper = createProviderWrapper(languageSpec, logger)
if (!(await tryInitLSP(ctx, wrapper, lspFactory, logger))) {
activateWithoutLSP(ctx, selector, wrapper)
}
}
/**
* Run the LSP factory return true if successful. Return false if an error is thrown.
*
* @param ctx The extension context.
* @param wrapper The provider wrapper.
* @param lspFactory An optional factory that registers an LSP client.
* @param logger An optional logger instance.
*/
export async function tryInitLSP(
ctx: sourcegraph.ExtensionContext,
wrapper: ProviderWrapper,
lspFactory?: LSPFactory,
logger: Logger = new RedactingLogger(console)
): Promise<boolean> {
if (!lspFactory) {
return false
}
try {
if (await lspFactory(ctx, wrapper)) {
return true
}
} catch (err) {
logger.error('Failed to initialize language server client', {
err,
})
}
return false
}
/**
* Create an LSP client and register providers with the given extension context.
* This function returns true if providers are registered and false otherwise.
*
* @param languageID The language identifier
* @param clientFactory A factory that initializes an LSP client.
* @param externalReferencesProviderFactory A factory that creates an external reference provider.
* @param logger An optional logger instance.
*/
export function initLSP<S extends { [key: string]: any }>(
languageID: string,
clientFactory: ClientFactory<S>,
externalReferencesProviderFactory: ExternalReferencesProviderFactory<S>,
logger: Logger = new RedactingLogger(console)
): (
ctx: sourcegraph.ExtensionContext,
providerWrapper: ProviderWrapper
) => Promise<boolean> {
return async (
ctx: sourcegraph.ExtensionContext,
providerWrapper: ProviderWrapper
): Promise<boolean> => {
const { settings, settingsSubject } = getSettings<S>(ctx)
const serverURL = settings[`${languageID}.serverUrl`]
if (!serverURL) {
logger.log('No language server url is configured')
return false
}
const accessToken = await getOrCreateAccessToken(
`${languageID}.accessToken`,
languageID
)
if (!accessToken) {
logger.log('No language server access token is available')
}
const sgUrl = sourcegraphURL(
settings[`${languageID}.sourcegraphUrl`],
languageID,
logger
)
const { client, featureOptionsSubject } = await clientFactory({
ctx,
serverURL,
sourcegraphURL: sgUrl,
accessToken,
providerWrapper,
settings,
})
const externalReferencesProvider = externalReferencesProviderFactory({
client,
settings,
sourcegraphServerURL: sgUrl,
sourcegraphClientURL: sourcegraph.internal.sourcegraphURL,
accessToken,
})
registerExternalReferenceProviderToggle(
ctx,
`${languageID}.impl`,
settingsSubject,
`${languageID}.showExternalReferences`,
featureOptionsSubject,
externalReferencesProvider
)
registerImplementationsPanel(ctx, `${languageID}.impl`)
logger.log('Language Server providers are active')
return true
}
}
/**
* Register definition, reference, and hover providers with LSIF and search-based providers.
*
* @param ctx The extension context.
* @param selector The document selector for which this extension is active.
* @param wrapper The provider wrapper.
*/
function activateWithoutLSP(
ctx: sourcegraph.ExtensionContext,
selector: sourcegraph.DocumentSelector,
wrapper: ProviderWrapper
): void {
ctx.subscriptions.add(
sourcegraph.languages.registerDefinitionProvider(
selector,
wrapper.definition()
)
)
ctx.subscriptions.add(
sourcegraph.languages.registerReferenceProvider(
selector,
wrapper.references()
)
)
ctx.subscriptions.add(
sourcegraph.languages.registerHoverProvider(selector, wrapper.hover())
)
// Do not try to register this provider on pre-3.18 instances as it
// didn't exist.
if (sourcegraph.languages.registerDocumentHighlightProvider) {
ctx.subscriptions.add(
sourcegraph.languages.registerDocumentHighlightProvider(
selector,
wrapper.documentHighlights()
)
)
}
}
/**
* Return the current settings and an observable that will yield the
* settings on change.
*
* @param ctx The extension context.
*/
function getSettings<S extends { [key: string]: any }>(
ctx: sourcegraph.ExtensionContext
): { settings: S; settingsSubject: Observable<S> } {
const settings = sourcegraph.configuration.get<S>().value
const settingsSubject: BehaviorSubject<S> = new BehaviorSubject<S>(settings)
ctx.subscriptions.add(
sourcegraph.configuration.subscribe(() =>
settingsSubject.next(sourcegraph.configuration.get<S>().value)
)
)
return { settings, settingsSubject }
}
/**
* Return the Sourcegraph URL from the current configuration.
*
* @param setting The user configured sourcegraph URL.
* @param languageID The language identifier.
* @param logger The logger instance.
*/
function sourcegraphURL(
setting: string | undefined,
languageID: string,
logger: Logger
): URL {
const url = setting || sourcegraph.internal.sourcegraphURL.toString()
try {
return new URL(url)
} catch (err) {
if (err.message?.includes('Invalid URL')) {
logger.error(
new Error(
[
`Invalid ${languageID}.sourcegraphUrl ${url} in your Sourcegraph settings.`,
'Make sure it is set to the address of Sourcegraph from the perspective of the language server (e.g. http://sourcegraph-frontend:30080).',
`Read the full documentation for more information: https://github.com/sourcegraph/sourcegraph-${languageID}`,
].join('\n')
)
)
}
throw err
}
}
/**
* When the current settings change, determine if we need to supply/revoke the
* externalReferencesProvider to the LSP client. This will cause the LSP client
* features to re-register the references provider via the extension context.
*
* @param ctx The extension context.
* @param implementationId The identifier of the registered locations provider.
* @param settingsSubject An observable of settings values.
* @param settingName The setting name to watch for changes.
* @param featureOptionsSubject The feature options to funnel changes into.
* @param externalReferencesProvider The external references provider to register.
*/
function registerExternalReferenceProviderToggle<
S extends { [key: string]: any }
>(
ctx: sourcegraph.ExtensionContext,
implementationId: string,
settingsSubject: Observable<S>,
settingName: string,
featureOptionsSubject: Subject<FeatureOptions>,
externalReferencesProvider: ReferencesProvider
): void {
ctx.subscriptions.add(
from(settingsSubject)
.pipe(
distinctUntilChanged(),
map(v => ({
implementationId,
...(v[settingName] ? { externalReferencesProvider } : {}),
}))
)
.subscribe(featureOptionsSubject)
)
}
/**
* Register a panel view that will hold the results from the LSP implementations provider.
*
* @param ctx The extension context.
* @param implementationId The identifier of the registered locations provider.
*/
function registerImplementationsPanel(
ctx: sourcegraph.ExtensionContext,
implementationId: string
): void {
const panelView = sourcegraph.app.createPanelView(implementationId)
panelView.title = 'Implementations'
panelView.component = { locationProvider: implementationId }
panelView.priority = 160
ctx.subscriptions.add(panelView)
}