Skip to content
This repository was archived by the owner on Mar 18, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion shared/badges.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Base64 } from 'js-base64'
import * as sourcegraph from 'sourcegraph'

/**
* Creates a base64-encoded image URI.
Expand Down Expand Up @@ -60,7 +61,7 @@ function makeInfoIcon(color: string): string {
/**
* The badge to send back on all results that come from searched-based data.
*/
export const impreciseBadge = {
export const impreciseBadge: sourcegraph.BadgeAttachmentRenderOptions = {
icon: makeInfoIcon('#ffffff'),
light: { icon: makeInfoIcon('#000000') },
hoverMessage:
Expand Down
69 changes: 53 additions & 16 deletions shared/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { LanguageSpec } from './language-specs/spec'
import { Logger } from './logging'
import { createProviders as createLSIFProviders } from './lsif/providers'
import { createProviders as createSearchProviders } from './search/providers'
import { TelemetryEmitter } from './telemetry'
import { asArray, mapArrayish } from './util/helpers'
import { noopAsyncGenerator, observableFromAsyncIterator } from './util/ix'

Expand Down Expand Up @@ -145,9 +146,12 @@ export function createDefinitionProvider(
doc: sourcegraph.TextDocument,
pos: sourcegraph.Position
): AsyncGenerator<sourcegraph.Definition | undefined, void, undefined> {
const emitter = new TelemetryEmitter()

let lastLsifResult: sourcegraph.Definition | undefined
for await (const lsifResult of lsifProvider(doc, pos)) {
if (lsifResult) {
await emitter.emitOnce('lsifDefinitions')
yield lsifResult
lastLsifResult = lsifResult
}
Expand All @@ -158,19 +162,24 @@ export function createDefinitionProvider(
}

if (lspProvider) {
// Delegate to LSP if it's available. Do not try to supplement
for await (const lspResult of lspProvider(doc, pos)) {
await emitter.emitOnce('lspDefinitions')
yield lspResult
}

// Do not try to supplement
// with additional search results as we have all the context we
// need for complete and precise results here.
yield* lspProvider(doc, pos)
return
}

for await (const searchResult of searchProvider(doc, pos)) {
// No results so far, fall back to search. Mark result as imprecise.
yield mapArrayish(searchResult, location => ({
...location,
badge: impreciseBadge,
}))
// No results so far, fall back to search. Mark the result as
// imprecise.
if (searchResult) {
await emitter.emitOnce('searchDefinitions')
yield badgeValues(searchResult, impreciseBadge)
}
}
}),
}
Expand Down Expand Up @@ -199,9 +208,12 @@ export function createReferencesProvider(
pos: sourcegraph.Position,
ctx: sourcegraph.ReferenceContext
): AsyncGenerator<sourcegraph.Location[] | null, void, undefined> {
const emitter = new TelemetryEmitter()

let lsifResults: sourcegraph.Location[] = []
for await (const lsifResult of lsifProvider(doc, pos, ctx)) {
if (lsifResult) {
await emitter.emitOnce('lsifReferences')
yield lsifResult
lsifResults = lsifResult
}
Expand All @@ -217,6 +229,7 @@ export function createReferencesProvider(

// Re-emit the last results from the previous provider
// so we do not overwrite what was emitted previously.
await emitter.emitOnce('lspReferences')
yield lsifResults.concat(filteredResults)
}

Expand All @@ -243,11 +256,9 @@ export function createReferencesProvider(
// Re-emit the last results from the previous provider so we
// do not overwrite what was emitted previously. Mark new results
// as imprecise.
await emitter.emitOnce('searchReferences')
yield lsifResults.concat(
filteredResults.map(location => ({
...location,
badge: impreciseBadge,
}))
asArray(badgeValues(filteredResults, impreciseBadge))
)
}
}),
Expand Down Expand Up @@ -275,9 +286,12 @@ export function createHoverProvider(
void,
undefined
> {
const emitter = new TelemetryEmitter()

let lastLsifResult: sourcegraph.Hover | null | undefined
for await (const lsifResult of lsifProvider(doc, pos)) {
if (lsifResult) {
await emitter.emitOnce('lsifHover')
yield lsifResult
lastLsifResult = lsifResult
}
Expand All @@ -288,23 +302,46 @@ export function createHoverProvider(
}

if (lspProvider) {
// Delegate to LSP if it's available. Do not try to supplement
// with additional search results as we have all the context we
// need for complete and precise results here.
yield* lspProvider(doc, pos)
// Delegate to LSP if it's available.
for await (const lspResult of lspProvider(doc, pos)) {
if (lspResult) {
await emitter.emitOnce('lspHover')
yield lspResult
}
}

// Do not try to supplement with additional search results
// as we have all the context we need for complete and precise
// results here.
return
}

for await (const searchResult of searchProvider(doc, pos)) {
// No results so far, fall back to search. Mark result as imprecise.
// No results so far, fall back to search. Mark the result as
// imprecise.
if (searchResult) {
await emitter.emitOnce('searchHover')
yield { ...searchResult, badge: impreciseBadge }
}
}
}),
}
}

/**
* Add a badge property to a single value or to a list of values. Returns the
* modified result in the same shape as the input.
*
* @param value The list of values, a single value, or null.
* @param badge The badge attachment.
*/
export function badgeValues<T extends object>(
value: T | T[] | null,
badge: sourcegraph.BadgeAttachmentRenderOptions
): sourcegraph.Badged<T> | sourcegraph.Badged<T>[] | null {
return mapArrayish(value, v => ({ ...v, badge }))
}

/**
* Converts an async generator provider into an observable provider. This also
* memoizes the previous result as a workaround for #1321 (below).
Expand Down
49 changes: 49 additions & 0 deletions shared/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as sourcegraph from 'sourcegraph'

/**
* A wrapper around telemetry events. A new instance of this class
* should be instantiated at the start of each action as it handles
* latency tracking.
*/
export class TelemetryEmitter {
private started: number
private emitted = new Set<string>()

constructor() {
this.started = Date.now()
}

/**
* Emit a telemetry event with a durationMs attribute only if the
* same action has not yet emitted for this instance.
*/
public emitOnce(action: string, args: object = {}): Promise<void> {
if (this.emitted.has(action)) {
return Promise.resolve()
}

this.emitted.add(action)
return this.emit(action, args)
}

/**
* Emit a telemetry event with a durationMs attribute.
*/
public async emit(action: string, args: object = {}): Promise<void> {
try {
await sourcegraph.commands.executeCommand(
'logTelemetryEvent',
`codeintel.${action}`,
{ ...args, durationMs: this.elapsed() }
)
} catch {
// Older version of Sourcegraph may have not registered this
// command, causing the promise to reject. We can safely ignore
// this condition.
}
}

private elapsed(): number {
return Date.now() - this.started
}
}
2 changes: 1 addition & 1 deletion shared/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function asArray<T>(value: T | T[] | null): T[] {
}

/**
* Apply a map function on a singel value or over a list of values. Returns the
* Apply a map function on a single value or over a list of values. Returns the
* modified result in the same shape as the input.
*
* @param value The list of values, a single value, or null.
Expand Down