Skip to content

Commit

Permalink
external references UI support, panel improvements (sourcegraph#1191)
Browse files Browse the repository at this point in the history
- Adds UI support for showing external references from extensions. External references are now shown in the same panel as local references. If a reference is emitted that is from a different repo, another column will show up to let the user select the repo. The current repo is always at the top and initially selected.
- Adds a "Group by file"/"Ungroup by file" panel action that adds in another column to filter references by file. Extensions can add other things here, such as toggling on/off certain reference providers.
- Adds a new `sourcegraph.languages.registerLocationProvider` call that generalizes location providers and panels. This can be used to show locations that are neither definitions nor references (such as implementations, type definitions, etc.).

This is part of sourcegraph#1187 and a requirement for deploying sourcegraph#584 to Sourcegraph.com (the switchover to the new extension-based code intel).


![screenshot from 2018-12-04 01-27-41](https://user-images.githubusercontent.com/1976/49432114-cee90200-f763-11e8-91dc-29b10b2dffae.png)

![screenshot from 2018-12-04 01-28-51](https://user-images.githubusercontent.com/1976/49432189-f6d86580-f763-11e8-839a-ff4dacd55d33.png)
  • Loading branch information
sqs authored Dec 4, 2018
1 parent 23a5556 commit d31e3e7
Show file tree
Hide file tree
Showing 59 changed files with 1,839 additions and 1,025 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ All notable changes to Sourcegraph are documented in this file.
### Removed

- The `siteID` site configuration option was removed because it is no longer needed. If you previously specified this in site configuration, a new, random site ID will be generated upon server startup. You can safely remove the existing `siteID` value from your site configuration after upgrading.
- The **Info** panel was removed. The information it presented can be viewed in the hover.

### Removed

Expand Down
49 changes: 31 additions & 18 deletions client/browser/src/libs/code_intelligence/code_intelligence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import { createPortal, render } from 'react-dom'
import { animationFrameScheduler, Observable, of, Subject, Subscription, Unsubscribable } from 'rxjs'
import { filter, map, mergeMap, observeOn, withLatestFrom } from 'rxjs/operators'

import { Model } from '../../../../../shared/src/api/client/model'
import { TextDocumentItem } from '../../../../../shared/src/api/client/types/textDocument'
import { Model, ViewComponentData } from '../../../../../shared/src/api/client/model'
import { ExtensionsControllerProps } from '../../../../../shared/src/extensions/controller'
import { getModeFromPath } from '../../../../../shared/src/languages'
import { PlatformContextProps } from '../../../../../shared/src/platform/context'
Expand Down Expand Up @@ -395,7 +394,7 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
subscriptions.add(hoverifier)

// Keeps track of all documents on the page since calling this function (should be once per page).
let visibleTextDocuments: TextDocumentItem[] = []
let visibleViewComponents: ViewComponentData[] = []

subscriptions.add(
of(document.body)
Expand Down Expand Up @@ -445,28 +444,42 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
baseContent = contents.baseContent
}

visibleTextDocuments = [
visibleViewComponents = [
// Either a normal file, or HEAD when codeView is a diff
{
uri: toURIWithPath(info),
languageId: getModeFromPath(info.filePath) || 'could not determine mode',
text: content!,
type: 'textEditor',
item: {
uri: toURIWithPath(info),
languageId: getModeFromPath(info.filePath) || 'could not determine mode',
text: content!,
},
selections: [],
isActive: true,
},
// All the currently open documents
...visibleTextDocuments,
// All the currently open documents, which are all now considered inactive.
...visibleViewComponents.map(c => ({ ...c, isActive: false })),
]
const roots: Model['roots'] = [{ uri: toRootURI(info) }]

// When codeView is a diff, add BASE too.
if (baseContent! && info.baseRepoPath && info.baseCommitID && info.baseFilePath) {
visibleTextDocuments.push({
uri: toURIWithPath({
repoPath: info.baseRepoPath,
commitID: info.baseCommitID,
filePath: info.baseFilePath,
}),
languageId: getModeFromPath(info.filePath) || 'could not determine mode',
text: baseContent!,
visibleViewComponents.push({
type: 'textEditor',
item: {
uri: toURIWithPath({
repoPath: info.baseRepoPath,
commitID: info.baseCommitID,
filePath: info.baseFilePath,
}),
languageId: getModeFromPath(info.filePath) || 'could not determine mode',
text: baseContent!,
},
// There is no notion of a selection on code hosts yet, so this is empty.
//
// TODO: Support interpreting GitHub #L1-2, etc., URL fragments as selections (and
// similar on other code hosts), or find some other way to get this info.
selections: [],
isActive: false,
})
roots.push({
uri: toRootURI({
Expand Down Expand Up @@ -501,7 +514,7 @@ function handleCodeHost(codeHost: CodeHost): Subscription {
})
}

extensionsController.services.model.model.next({ roots, visibleTextDocuments })
extensionsController.services.model.model.next({ roots, visibleViewComponents })
}

const resolveContext: ContextResolver = ({ part }) => ({
Expand Down
8 changes: 6 additions & 2 deletions client/browser/src/shared/components/CodeViewToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ export class CodeViewToolbar extends React.Component<CodeViewToolbarProps, CodeV
actionItemClass="btn btn-sm tooltipped tooltipped-n BtnGroup-item"
location={this.props.location}
scope={{
uri: toURIWithPath(this.props),
languageId: getModeFromPath(this.props.filePath) || 'could not determine mode',
type: 'textEditor',
item: {
uri: toURIWithPath(this.props),
languageId: getModeFromPath(this.props.filePath) || 'could not determine mode',
},
selections: [],
}}
/>
)}
Expand Down
38 changes: 38 additions & 0 deletions doc/extensions/authoring/builtin_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,41 @@ This can be used to identify the current user, perform a search, fetch a file's
- Returns: `void`

Opens a view in the panel. The view must have been previously created by the extension using `sourcegraph.app.createPanelView`.

### executeLocationProvider

- Parameters:
1. `id` (string) - The location provider ID. This is defined by the extension as the first argument to `sourcegraph.languages.registerLocationProvider`.
1. `uri` (string) - The URI of a text document (usually the currently active document).
1. `position` (Position, `{line: number, character: number}`) - A position in a text document (usually the cursor position).
- Returns: `Location[] | Promise<Location[]>`

Executes a location provider and returns the results (a list of locations). Location providers are registered by extensions calling `sourcegraph.languages.registerLocationProvider`. They are the general form of definition providers and reference providers; they accept a document position and return a list of related locations in other files.

The `executeLocationProvider` command returns results to the caller but does not display them to the user.

Known issues:

- If the location provider returns an `Observable` (stream of values), the `executeLocationProvider` only returns a promise that resolves with the first emission. It does not return an observable.

#### Opening a panel with a list of file locations

This example shows how to open a panel view that displays the location results from a location provider.

In your extension code, create a location provider and panel view, and link them together:

```typescript
// Create the panel view.
const panelView = sourcegraph.app.createPanelView('fooPanel')
panelView.title = 'Foo'

// Create a location provider.
sourcegraph.languages.registerLocationProvider('fooLocations', ['*'], {
provideLocations: () => [],
})

// Tell the panel view to display the location provider's results.
panelView.component = { locationProvider: 'fooLocations' }
```

Now, execute the [`openPanel`](builtin_commands.md#openPanel) command with the first argument `fooPanel`. The panel will display the results from the location provider for the currently active document position.
96 changes: 95 additions & 1 deletion packages/sourcegraph-extension-api/src/sourcegraph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,23 @@ declare module 'sourcegraph' {
constructor(uri: URI, rangeOrPosition?: Range | Position)
}

/**
* A text document, such as a file in a repository.
*/
export interface TextDocument {
/**
* The URI of the text document.
*/
readonly uri: string

/**
* The language of the text document.
*/
readonly languageId: string

/**
* The text contents of the text document.
*/
readonly text: string
}

Expand Down Expand Up @@ -389,6 +403,11 @@ declare module 'sourcegraph' {
*/
visibleViewComponents: ViewComponent[]

/**
* The currently active view component in the window.
*/
activeViewComponent: ViewComponent | undefined

/**
* Show a notification message to the user that does not require interaction or steal focus.
*
Expand Down Expand Up @@ -509,10 +528,26 @@ declare module 'sourcegraph' {
type: 'CodeEditor'

/**
* The text document that is open in this editor.
* The text document that is open in this editor. The document remains the same for the entire lifetime of
* this editor.
*/
readonly document: TextDocument

/**
* The primary selection in this text editor. This is equivalent to `CodeEditor.selections[0] || null`.
*
* @todo Make this non-readonly.
*/
readonly selection: Selection | null

/**
* The selections in this text editor. A text editor has zero or more selections. The primary selection
* ({@link CodeEditor#selection}), if any selections exist, is always at index 0.
*
* @todo Make this non-readonly.
*/
readonly selections: Selection[]

/**
* Draw decorations on this editor.
*
Expand All @@ -535,6 +570,20 @@ declare module 'sourcegraph' {
* The content to show in the panel view. Markdown is supported.
*/
content: string

/**
* The priority of this panel view. A higher value means that the item is shown near the beginning (usually
* the left side).
*/
priority: number

/**
* Display the results of the location provider (with the given ID) in this panel below the
* {@link PanelView#contents}.
*
* @internal Experimental. Subject to change or removal without notice.
*/
component: { locationProvider: string } | null
}

/**
Expand Down Expand Up @@ -762,11 +811,15 @@ declare module 'sourcegraph' {

/**
* A type definition provider implements the "go-to-type-definition" feature.
*
* @deprecated Use {@link LocationProvider} and {@link sourcegraph.languages.registerLocationProvider} instead.
*/
export interface TypeDefinitionProvider {
/**
* Provide the type definition of the symbol at the given position and document.
*
* @deprecated Use {@link LocationProvider} and {@link sourcegraph.languages.registerLocationProvider}
* instead.
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @return A type definition location, or an array of definitions, or `null` if there is no type
Expand All @@ -777,11 +830,15 @@ declare module 'sourcegraph' {

/**
* An implementation provider implements the "go-to-implementations" and "go-to-interfaces" features.
*
* @deprecated Use {@link LocationProvider} and {@link sourcegraph.languages.registerLocationProvider} instead.
*/
export interface ImplementationProvider {
/**
* Provide the implementations of the symbol at the given position and document.
*
* @deprecated Use {@link LocationProvider} and {@link sourcegraph.languages.registerLocationProvider}
* instead.
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @return Implementation locations, or `null` if there are none.
Expand Down Expand Up @@ -817,6 +874,21 @@ declare module 'sourcegraph' {
): ProviderResult<Location[]>
}

/**
* A location provider implements features such as "find implementations" and "find type definition". It is the
* general form of {@link DefinitionProvider} and {@link ReferenceProvider}.
*/
export interface LocationProvider {
/**
* Provide related locations for the symbol at the given position and document.
*
* @param document The document in which the command was invoked.
* @param position The position at which the command was invoked.
* @return Related locations, or `null` if there are none.
*/
provideLocations(document: TextDocument, position: Position): ProviderResult<Location[]>
}

export namespace languages {
/**
* Registers a hover provider, which returns a formatted hover message (intended for display in a tooltip)
Expand Down Expand Up @@ -855,6 +927,7 @@ declare module 'sourcegraph' {
* the results are merged. A failing provider (rejected promise or exception) will not cause the whole
* operation to fail.
*
* @deprecated Use {@link LocationProvider} and {@link registerLocationProvider} instead.
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A type definition provider.
* @return An unsubscribable to unregister this provider.
Expand All @@ -871,6 +944,7 @@ declare module 'sourcegraph' {
* the results are merged. A failing provider (rejected promise or exception) will not cause the whole
* operation to fail.
*
* @deprecated Use {@link LocationProvider} and {@link registerLocationProvider} instead.
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider An implementation provider.
* @return An unsubscribable to unregister this provider.
Expand All @@ -895,6 +969,26 @@ declare module 'sourcegraph' {
selector: DocumentSelector,
provider: ReferenceProvider
): Unsubscribable

/**
* Registers a generic provider of a list of locations. It is the general form of
* {@link registerDefinitionProvider} and {@link registerReferenceProvider}. It is intended for "find
* implementations", "find type definition", and other similar features.
*
* The provider can be executed with the `executeLocationProvider` builtin command, passing the {@link id}
* as the first argument. For more information, see
* https://docs.sourcegraph.com/extensions/authoring/builtin_commands#executeLocationProvider.
*
* @param id An identifier for this location provider that distinguishes it from other location providers.
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider A location provider.
* @return An unsubscribable to unregister this provider.
*/
export function registerLocationProvider(
id: string,
selector: DocumentSelector,
provider: LocationProvider
): Unsubscribable
}

/**
Expand Down
5 changes: 4 additions & 1 deletion shared/src/actions/ActionsNavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface Props extends ActionsProps {
*/
wrapInList?: boolean

listClass?: string
actionItemClass?: string
listItemClass?: string
}
Expand Down Expand Up @@ -67,7 +68,9 @@ export class ActionsNavItems extends React.PureComponent<Props, ActionsState> {
</li>
))
if (this.props.wrapInList) {
return actionItems.length > 0 ? <ul className="nav">{actionItems}</ul> : null
return actionItems.length > 0 ? (
<ul className={`nav ${this.props.listClass || ''}`}>{actionItems}</ul>
) : null
}
return <>{actionItems}</>
}
Expand Down
7 changes: 2 additions & 5 deletions shared/src/api/client/api/documents.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Observable, Subscription } from 'rxjs'
import { TextDocument } from 'sourcegraph'
import { createProxyAndHandleRequests } from '../../common/proxy'
import { ExtDocumentsAPI } from '../../extension/api/documents'
import { Connection } from '../../protocol/jsonrpc2/connection'
import { TextDocumentItem } from '../types/textDocument'
import { SubscriptionMap } from './common'

/** @internal */
Expand All @@ -11,10 +11,7 @@ export class ClientDocuments {
private registrations = new SubscriptionMap()
private proxy: ExtDocumentsAPI

constructor(
connection: Connection,
modelTextDocuments: Observable<Pick<TextDocument, 'uri' | 'languageId' | 'text'>[] | null>
) {
constructor(connection: Connection, modelTextDocuments: Observable<TextDocumentItem[] | null>) {
this.proxy = createProxyAndHandleRequests('documents', connection, this)

this.subscriptions.add(
Expand Down
Loading

0 comments on commit d31e3e7

Please sign in to comment.