Skip to content
This repository has been archived by the owner on Mar 18, 2024. It is now read-only.

Commit

Permalink
hover source code intel tooltips (#423)
Browse files Browse the repository at this point in the history
* dummy

* add code intelligence source alerts to the code intel extensions

* make code intelligence source alerts language aware

* improve structure

* style fixes

* bump sg version

* bump extension-api-stubs

* yarn deduplicate

* yarn update-graphql-schema

* hover text -> hover data
  • Loading branch information
gbrik committed Jul 16, 2020
1 parent eb940b3 commit f4c51a9
Show file tree
Hide file tree
Showing 11 changed files with 243 additions and 93 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -35,14 +35,14 @@
"mz": "^2.7.0",
"path-browserify": "^1.0.1",
"rxjs": "^6.6.0",
"sourcegraph": "^24.6.0",
"sourcegraph": "^24.7.0",
"tagged-template-noop": "^2.1.1",
"uuid": "^8.2.0",
"vscode-languageserver-protocol": "3.14.1"
},
"devDependencies": {
"@sourcegraph/eslint-config": "^0.19.15",
"@sourcegraph/extension-api-stubs": "^1.1.1",
"@sourcegraph/extension-api-stubs": "^1.2.1",
"@sourcegraph/prettierrc": "^3.0.3",
"@sourcegraph/tsconfig": "^4.0.1",
"@types/fs-extra": "9.0.1",
Expand Down
1 change: 1 addition & 0 deletions schema/schema.graphql
Expand Up @@ -1164,6 +1164,7 @@ type Query {
# Include cloned repositories.
cloned: Boolean = true
# Include repositories that are currently being cloned.
# DEPRECATED: This will be removed.
cloneInProgress: Boolean = true
# Include repositories that are not yet cloned and for which cloning is not in progress.
notCloned: Boolean = true
Expand Down
73 changes: 73 additions & 0 deletions shared/hoverAlerts.ts
@@ -0,0 +1,73 @@
import * as sourcegraph from 'sourcegraph'

export const lsif: sourcegraph.Badged<sourcegraph.HoverAlert>[] = [
{
summary: {
kind: sourcegraph.MarkupKind.Markdown,
value: 'Semantic result. [Learn more.](https://docs.sourcegraph.com/user/code_intelligence/lsif)',
},
badge: {
kind: 'info',
hoverMessage:
"This hover data comes from a pre-computed semantic index of this project's source. Click to learn how to add this capability to all of your projects!",
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/lsif',
},
type: 'LSIFAvailableNoCaveat',
},
]

export const lsp: sourcegraph.Badged<sourcegraph.HoverAlert>[] = [
{
summary: {
kind: sourcegraph.MarkupKind.Markdown,
value: 'Language server result. [Get LSIF.](https://docs.sourcegraph.com/user/code_intelligence/lsif)',
},
badge: {
kind: 'info',
hoverMessage:
'This hover data comes from a language server running in the cloud. Click to learn how to improve the reliability of this result by enabling semantic indexing.',
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/lsif',
},
},
]

export const searchLSIFSupportRobust: sourcegraph.Badged<sourcegraph.HoverAlert>[] = [
{
summary: {
kind: sourcegraph.MarkupKind.Markdown,
value: 'Search-based result. [Get semantics.](https://docs.sourcegraph.com/user/code_intelligence/lsif)',
},
badge: {
kind: 'info',
hoverMessage:
'This hover data is generated by a heuristic text-based search. Click to learn how to make these results precise by enabling semantic indexing for this project.',
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/lsif',
},
},
]

export const searchLSIFSupportExperimental: sourcegraph.Badged<sourcegraph.HoverAlert>[] = [
{
summary: {
kind: sourcegraph.MarkupKind.Markdown,
value: 'Search-based result. [Learn more.](https://docs.sourcegraph.com/user/code_intelligence/lsif)',
},
badge: {
kind: 'info',
hoverMessage:
"This hover data is generated by a heuristic text-based search. Existing semantic indexers for this language aren't totally robust yet, but you can click here to learn how to give them a try.",
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/lsif',
},
type: 'SearchResultExperimentalLSIFSupport',
},
]

export const searchLSIFSupportNone: sourcegraph.Badged<sourcegraph.HoverAlert>[] = [
{
summary: {
kind: sourcegraph.MarkupKind.Markdown,
value: 'Search-based result. [Learn more.](https://docs.sourcegraph.com/user/code_intelligence/lsif)',
},
type: 'SearchResultNoLSIFSupport',
},
]
3 changes: 2 additions & 1 deletion shared/language-specs/cpp.ts
@@ -1,5 +1,5 @@
import { cStyleComment } from './comments'
import { FilterContext, LanguageSpec, Result } from './spec'
import { FilterContext, LanguageSpec, Result, LSIFSupport } from './spec'
import { dotToSlash, extractFromLines, filterResultsByImports, removeExtension } from './util'

/**
Expand Down Expand Up @@ -48,6 +48,7 @@ export const cppSpec: LanguageSpec = {
],
commentStyles: [cStyleComment],
filterDefinitions,
lsifSupport: LSIFSupport.Experimental,
}

export const cudaSpec: LanguageSpec = {
Expand Down
3 changes: 2 additions & 1 deletion shared/language-specs/go.ts
@@ -1,6 +1,6 @@
import * as path from 'path'
import { slashPattern } from './comments'
import { FilterContext, LanguageSpec, Result } from './spec'
import { FilterContext, LanguageSpec, Result, LSIFSupport } from './spec'
import { extractFromLines, filterResults } from './util'

/**
Expand Down Expand Up @@ -43,4 +43,5 @@ export const goSpec: LanguageSpec = {
fileExts: ['go'],
commentStyles: [{ lineRegex: slashPattern }],
filterDefinitions,
lsifSupport: LSIFSupport.Robust,
}
3 changes: 2 additions & 1 deletion shared/language-specs/java.ts
@@ -1,6 +1,6 @@
import * as path from 'path'
import { javaStyleComment } from './comments'
import { FilterContext, LanguageSpec, Result } from './spec'
import { FilterContext, LanguageSpec, Result, LSIFSupport } from './spec'
import { extractFromLines, filterResultsByImports, slashToDot } from './util'

/**
Expand Down Expand Up @@ -33,4 +33,5 @@ export const javaSpec: LanguageSpec = {
fileExts: ['java'],
commentStyles: [javaStyleComment],
filterDefinitions,
lsifSupport: LSIFSupport.Experimental,
}
12 changes: 12 additions & 0 deletions shared/language-specs/spec.ts
Expand Up @@ -35,6 +35,18 @@ export interface LanguageSpec {
* results from non-imported files).
*/
filterDefinitions?: FilterDefinitions

/**
* Affects messaging about adding LSIF indexing when hovering over symbols
* from this language.
*/
lsifSupport?: LSIFSupport
}

export enum LSIFSupport {
None = 'none',
Experimental = 'experimental',
Robust = 'robust',
}

/**
Expand Down
3 changes: 2 additions & 1 deletion shared/language-specs/typescript.ts
@@ -1,6 +1,6 @@
import * as path from 'path'
import { cStyleComment } from './comments'
import { FilterContext, LanguageSpec, Result } from './spec'
import { FilterContext, LanguageSpec, Result, LSIFSupport } from './spec'
import { extractFromLines, filterResultsByImports, removeExtension } from './util'

/**
Expand Down Expand Up @@ -36,4 +36,5 @@ export const typescriptSpec: LanguageSpec = {
fileExts: ['ts', 'tsx', 'js', 'jsx'],
commentStyles: [cStyleComment],
filterDefinitions,
lsifSupport: LSIFSupport.Robust,
}
36 changes: 33 additions & 3 deletions shared/providers.test.ts
Expand Up @@ -9,6 +9,8 @@ import {
createReferencesProvider,
createDocumentHighlightProvider,
} from './providers'
import * as HoverAlerts from './hoverAlerts'
import { LSIFSupport } from './language-specs/spec'

const textDocument = createStubTextDocument({
uri: 'https://sourcegraph.test/repo@rev/-/raw/foo.ts',
Expand Down Expand Up @@ -174,31 +176,59 @@ describe('createReferencesProvider', () => {
describe('createHoverProvider', () => {
it('uses LSIF definitions as source of truth', async () => {
const result = createHoverProvider(
LSIFSupport.None,
() => asyncGeneratorFromValues([hover1, hover2]),
() => asyncGeneratorFromValues([hover5]),
() => asyncGeneratorFromValues([hover3, hover4])
).provideHover(textDocument, position) as Observable<sourcegraph.Badged<sourcegraph.Hover>>

assert.deepStrictEqual(await gatherValues(result), [hover1, hover2])
assert.deepStrictEqual(await gatherValues(result), [{ ...hover1, alerts: HoverAlerts.lsif }, hover2])
})

it('falls back to LSP when LSIF results are not found', async () => {
const result = createHoverProvider(
LSIFSupport.None,
() => asyncGeneratorFromValues([]),
() => asyncGeneratorFromValues([hover3]),
() => asyncGeneratorFromValues([hover1, hover2])
).provideHover(textDocument, position) as Observable<sourcegraph.Badged<sourcegraph.Hover>>

assert.deepStrictEqual(await gatherValues(result), [hover1, hover2])
assert.deepStrictEqual(await gatherValues(result), [{ ...hover1, alerts: HoverAlerts.lsp }, hover2])
})

it('falls back to basic when precise results are not found', async () => {
const result = createHoverProvider(
LSIFSupport.None,
() => asyncGeneratorFromValues([]),
() => asyncGeneratorFromValues([hover3])
).provideHover(textDocument, position) as Observable<sourcegraph.Badged<sourcegraph.Hover>>

assert.deepStrictEqual(await gatherValues(result), [{ ...hover3, badge: impreciseBadge }])
assert.deepStrictEqual(await gatherValues(result), [{ ...hover3, alerts: HoverAlerts.searchLSIFSupportNone }])
})

it('alerts search results correctly with experimental LSIF support', async () => {
const result = createHoverProvider(
LSIFSupport.Experimental,
() => asyncGeneratorFromValues([]),
() => asyncGeneratorFromValues([hover3])
).provideHover(textDocument, position) as Observable<sourcegraph.Badged<sourcegraph.Hover>>

assert.deepStrictEqual(await gatherValues(result), [
{
...hover3,
alerts: HoverAlerts.searchLSIFSupportExperimental,
},
])
})

it('alerts search results correctly with robust LSIF support', async () => {
const result = createHoverProvider(
LSIFSupport.Robust,
() => asyncGeneratorFromValues([]),
() => asyncGeneratorFromValues([hover3])
).provideHover(textDocument, position) as Observable<sourcegraph.Badged<sourcegraph.Hover>>

assert.deepStrictEqual(await gatherValues(result), [{ ...hover3, alerts: HoverAlerts.searchLSIFSupportRobust }])
})
})

Expand Down
48 changes: 43 additions & 5 deletions shared/providers.ts
Expand Up @@ -2,13 +2,14 @@ import { NEVER, Observable } from 'rxjs'
import { shareReplay } from 'rxjs/operators'
import * as sourcegraph from 'sourcegraph'
import { impreciseBadge } from './badges'
import { LanguageSpec } from './language-specs/spec'
import { LanguageSpec, LSIFSupport } 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, nonEmpty } from './util/helpers'
import { noopAsyncGenerator, observableFromAsyncIterator } from './util/ix'
import * as HoverAlerts from './hoverAlerts'

export interface Providers {
definition: DefinitionProvider
Expand Down Expand Up @@ -120,7 +121,12 @@ export function createProviderWrapper(languageSpec: LanguageSpec, logger: Logger
},

hover: (lspProvider?: HoverProvider) => {
const provider = createHoverProvider(lsifProviders.hover, searchProviders.hover, lspProvider)
const provider = createHoverProvider(
languageSpec.lsifSupport || LSIFSupport.None,
lsifProviders.hover,
searchProviders.hover,
lspProvider
)
wrapped.hover = provider
return provider
},
Expand Down Expand Up @@ -283,6 +289,7 @@ export function createReferencesProvider(
* @param lspProvider An optional LSP-based hover provider.
*/
export function createHoverProvider(
lsifSupport: LSIFSupport,
lsifProvider: HoverProvider,
searchProvider: HoverProvider,
lspProvider?: HoverProvider
Expand All @@ -298,7 +305,11 @@ export function createHoverProvider(
for await (const lsifResult of lsifProvider(textDocument, position)) {
if (lsifResult) {
await emitter.emitOnce('lsifHover')
yield lsifResult
if (hasPreciseResult) {
yield lsifResult
} else {
yield { ...lsifResult, alerts: HoverAlerts.lsif }
}
hasPreciseResult = true
}
}
Expand All @@ -309,10 +320,19 @@ export function createHoverProvider(

if (lspProvider) {
// Delegate to LSP if it's available.
let hasLSPResult = false
for await (const lspResult of lspProvider(textDocument, position)) {
if (lspResult) {
await emitter.emitOnce('lspHover')
yield lspResult
if (hasLSPResult) {
yield lspResult
} else {
yield {
...lspResult,
alerts: HoverAlerts.lsp,
}
}
hasLSPResult = true
}
}

Expand All @@ -322,12 +342,30 @@ export function createHoverProvider(
return
}

let hasSearchResult = false
for await (const searchResult of searchProvider(textDocument, position)) {
// No results so far, fall back to search. Mark the result as
// imprecise.
if (searchResult) {
await emitter.emitOnce('searchHover')
yield { ...searchResult, badge: impreciseBadge }
if (hasSearchResult) {
yield searchResult
} else {
let alerts: sourcegraph.Badged<sourcegraph.HoverAlert>[] = []
if (lsifSupport === LSIFSupport.None) {
alerts = HoverAlerts.searchLSIFSupportNone
} else if (lsifSupport === LSIFSupport.Experimental) {
alerts = HoverAlerts.searchLSIFSupportExperimental
} else if (lsifSupport === LSIFSupport.Robust) {
alerts = HoverAlerts.searchLSIFSupportRobust
}

yield {
...searchResult,
alerts,
}
}
hasSearchResult = true
}
}
}),
Expand Down

0 comments on commit f4c51a9

Please sign in to comment.