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
60 changes: 53 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,37 @@
"actions": [
{
"id": "basicCodeIntel.toggle",
"command": "basicCodeIntel.toggle",
"command": "updateConfiguration",
"commandArguments": [
[
"basicCodeIntel.enabled"
],
"${!config.basicCodeIntel.enabled}",
null,
"json"
],
"title": "${config.basicCodeIntel.enabled && \"Disable\" || \"Enable\"} fuzzy def/ref matching",
"category": "Basic code intel",
"actionItem": {
"label": "${config.basicCodeIntel.enabled && \"Hide\" || \"Show\"} fuzzy matches",
"description": "${config.basicCodeIntel.enabled && \"Hide\" || \"Show\"} matches found using imprecise text search"
}
},
{
"id": "basicCodeIntel.toggleSymbols",
"command": "updateConfiguration",
"commandArguments": [
[
"basicCodeIntel.definition.symbols"
],
"${((config.basicCodeIntel.definition.symbols == \"local\") || (config.basicCodeIntel.definition.symbols == \"always\")) && \"never\" || \"local\"}"
],
"title": "${((config.basicCodeIntel.definition.symbols == \"local\") || (config.basicCodeIntel.definition.symbols == \"always\")) && \"Disable\" || \"Enable\"} hints from ctags",
"category": "Basic code intel"
},
{
"id": "basicCodeIntel.old.togglePreciseFuzzy",
"command": "basicCodeIntel.old.togglePreciseFuzzy",
"title": "${config.basicCodeIntel.enabled && \"Switch to full code intelligence\" || \"Switch to basic code intelligence\"}",
"actionItem": {
"label": "${config.basicCodeIntel.enabled && \"Fuzzy\" || \"Precise\"}",
Expand All @@ -22,14 +52,28 @@
"menus": {
"editor/title": [
{
"action": "basicCodeIntel.toggle",
"when": "resource"
"action": "basicCodeIntel.old.togglePreciseFuzzy",
"when": "resource && ((clientApplication.extensionAPIVersion.major || 0) < 3)"
}
],
"commandPalette": [
{
"action": "basicCodeIntel.toggle",
"when": "resource"
},
{
"action": "basicCodeIntel.toggleSymbols",
"when": "resource"
},
{
"action": "basicCodeIntel.old.togglePreciseFuzzy",
"when": "resource && ((clientApplication.extensionAPIVersion.major || 0) < 3)"
}
],
"panel/toolbar": [
{
"action": "basicCodeIntel.toggle",
"when": "((panel.activeView.id == 'def') || (panel.activeView.id == 'references')) && clientApplication.extensionAPIVersion.major >= 3"
}
]
},
Expand All @@ -45,9 +89,9 @@
"type": "string",
"description": "Whether to use symbol search to complete goto definition requests. 'Local' means issue a symbol search locally and if none are found, fall back to text search globally.",
"enum": [
"no",
"never",
"local",
"yes"
"always"
]
},
"basicCodeIntel.debug.traceSearch": {
Expand Down Expand Up @@ -104,12 +148,14 @@
"parcel-bundler": "^1.10.0",
"prettier": "^1.14.2",
"source-map-support": "^0.5.9",
"sourcegraph": "^19.2.0",
"sourcegraph": "^19.3.0",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-language-service": "^0.9.9",
"typescript": "^3.2.1"
},
"dependencies": {},
"dependencies": {
"rxjs": "^6.3.3"
},
"sideEffects": false
}
4 changes: 2 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as sourcegraph from 'sourcegraph'
import { Config } from './handler'
import { Settings } from './handler'

/**
* Result represents a search result returned from the Sourcegraph API.
Expand All @@ -22,7 +22,7 @@ export class API {
private get traceSearch(): boolean {
return Boolean(
sourcegraph.configuration
.get<Config>()
.get<Settings>()
.get('basicCodeIntel.debug.traceSearch')
)
}
Expand Down
173 changes: 135 additions & 38 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,142 @@
import * as sourcegraph from 'sourcegraph'
import { Handler, Config } from './handler'
import { from, Observable } from 'rxjs'
import { first, map, distinctUntilChanged, finalize } from 'rxjs/operators'
import { Handler, Settings, DOCUMENT_SELECTOR } from './handler'

export async function activate(): Promise<void> {
// No-op for Sourcegraph versions prior to 3.0-preview
const DUMMY_CTX = { subscriptions: { add: (_unsubscribable: any) => void 0 } }

export function activate(ctx: sourcegraph.ExtensionContext = DUMMY_CTX): void {
const h = new Handler()

sourcegraph.commands.registerCommand('basicCodeIntel.toggle', () => {
// Toggle between 2 states:
//
// Enabled: basicCodeIntel.enabled = true and extensions.langserver/* = false
//
// Disabled: basicCodeIntel.enabled = false and extensions.langserver/* = true
//
// These 2 states are not inverses of each other. Enabling and disabling basic code
// intel might enable or disable langserver extensions in a way that the user does not
// expect or desire.
const config = sourcegraph.configuration.get<
Config & { extensions: { [id: string]: boolean } }
>()

const newEnabled = !config.get('basicCodeIntel.enabled')
config
.update('basicCodeIntel.enabled', newEnabled)
.then(async () => {
const extensions = { ...(config.get('extensions') || {}) }
for (const extensionID of Object.keys(extensions)) {
if (
extensionID.startsWith('langserver/') ||
extensionID.includes('/langserver')
) {
extensions[extensionID] = !newEnabled
}
ctx.subscriptions.add(
sourcegraph.commands.registerCommand(
'basicCodeIntel.old.togglePreciseFuzzy',
() => {
// Toggle between 2 states:
//
// Enabled: basicCodeIntel.enabled = true and extensions.langserver/* = false
//
// Disabled: basicCodeIntel.enabled = false and extensions.langserver/* = true
//
// These 2 states are not inverses of each other. Enabling and disabling basic code
// intel might enable or disable langserver extensions in a way that the user does not
// expect or desire.
const config = sourcegraph.configuration.get<
Settings & { extensions: { [id: string]: boolean } }
>()

const newEnabled = !config.get('basicCodeIntel.enabled')
config
.update('basicCodeIntel.enabled', newEnabled)
.then(async () => {
const extensions = {
...(config.get('extensions') || {}),
}
for (const extensionID of Object.keys(extensions)) {
if (
extensionID.startsWith('langserver/') ||
extensionID.includes('/langserver')
) {
extensions[extensionID] = !newEnabled
}
}
await config.update('extensions', extensions)
})
.catch(err => console.error(err))
}
)
)

ctx.subscriptions.add(
reregisterWhenEnablementChanges(() =>
sourcegraph.languages.registerDefinitionProvider(
DOCUMENT_SELECTOR,
{
provideDefinition: (doc, pos) =>
enabledOrNull(() =>
observableOrPromiseCompat(h.definition(doc, pos))
),
}
await config.update('extensions', extensions)
)
)
)
ctx.subscriptions.add(
reregisterWhenEnablementChanges(() =>
sourcegraph.languages.registerReferenceProvider(DOCUMENT_SELECTOR, {
provideReferences: (doc, pos) =>
enabledOrNull(() =>
observableOrPromiseCompat(h.references(doc, pos))
),
})
.catch(err => console.error(err))
})

sourcegraph.languages.registerDefinitionProvider(['*'], {
provideDefinition: (doc, pos) => h.definition(doc, pos),
})
sourcegraph.languages.registerReferenceProvider(['*'], {
provideReferences: (doc, pos) => h.references(doc, pos),
})
)
)
}

const settingsSubscribable = new Observable<Settings>(sub => {
sub.next(sourcegraph.configuration.get().value)
return sourcegraph.configuration.subscribe(() =>
sub.next(sourcegraph.configuration.get().value)
)
})

function enabledOrNull<T>(provider: () => T): T | null {
if (
!sourcegraph.configuration.get<Settings>().value[
'basicCodeIntel.enabled'
]
) {
return null
}
return provider()
}

/**
* This makes it so that basicCodeIntel.toggle (the "Show/hide fuzzy matches" button) immediately takes effect and
* changes the locations that are currently being displayed in the panel.
*
* If we used an observable instead, it would always show the loading indicator.
*/
function reregisterWhenEnablementChanges(
register: () => sourcegraph.Unsubscribable
): sourcegraph.Unsubscribable {
let registration: sourcegraph.Unsubscribable | undefined
return from(settingsSubscribable)
.pipe(
distinctUntilChanged(
(a, b) =>
Boolean(a['basicCodeIntel.enabled']) ===
Boolean(b['basicCodeIntel.enabled']) &&
a['basicCodeIntel.definition.symbols'] ===
b['basicCodeIntel.definition.symbols']
),
map(() => {
if (registration) {
registration.unsubscribe()
}
registration = register()
}),
finalize(() => {
if (registration) {
registration.unsubscribe()
registration = undefined
}
})
)
.subscribe()
}

function observableOrPromiseCompat<T>(
result: Observable<T> | Promise<T>
): sourcegraph.ProviderResult<T> {
// HACK: Earlier extension API versions did not support providers returning observables. We can detect whether
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should plan to handle the issue of extension API breakage differently - IMO there should be no backwards compatibility handling in extension code, it should be handled at the registry/extension host level.

eg.:

  • If the user has activated the Sourcegraph extension E, which is only compatible with extension API vN, and his browser extension packages extension API vN+1, he receives a notification that "Incompatible extension E could not be activated"
  • If the user's browser extension packages extension API vN, the extension registry will provide the latest version of E that was compatible with extension API vN, even if the very latest version of E on the registry was coded against vN+1

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// the extension API version is compatible by checking for the presence of registerLocationProvider, which was
// added around the same time.
const supportsProvidersReturningObservables = !!sourcegraph.languages
.registerLocationProvider
return supportsProvidersReturningObservables
? from(result)
: from(result)
.pipe(first())
.toPromise()
}
32 changes: 14 additions & 18 deletions src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ function initFileExtToTerm() {
}
initFileExtToTerm()

/**
* Selects documents that the extension works on.
*/
export const DOCUMENT_SELECTOR: sourcegraph.DocumentSelector = fileExtsSets
.reduce((all, exts) => all.concat(exts), [])
.map(ext => ({ pattern: `*.${ext}` }))

/**
* fileExtTerm returns the search term to use to filter to specific file extensions
*/
Expand Down Expand Up @@ -127,9 +134,9 @@ function resultToLocation(res: Result): sourcegraph.Location {
/**
* @see package.json contributes.configuration section for the configuration schema.
*/
export interface Config {
export interface Settings {
['basicCodeIntel.enabled']?: boolean
['basicCodeIntel.definition.symbols']?: 'local' | 'always'
['basicCodeIntel.definition.symbols']?: 'never' | 'local' | 'always'
['basicCodeIntel.debug.traceSearch']?: boolean
}

Expand All @@ -139,23 +146,16 @@ export class Handler {
*/
public api = new API()

private get enabled(): boolean {
return Boolean(
sourcegraph.configuration
.get<Config>()
.get('basicCodeIntel.enabled')
)
}

async definition(
doc: sourcegraph.TextDocument,
pos: sourcegraph.Position,
symbols = sourcegraph.configuration
.get<Config>()
.get<Settings>()
.get('basicCodeIntel.definition.symbols')
): Promise<sourcegraph.Location | sourcegraph.Location[] | null> {
if (!this.enabled) {
return null
): Promise<sourcegraph.Location[] | null> {
// Default to using local symbols lookup.
if (!symbols) {
symbols = 'local'
}

const lines = doc.text.split('\n')
Expand Down Expand Up @@ -208,10 +208,6 @@ export class Handler {
doc: sourcegraph.TextDocument,
pos: sourcegraph.Position
): Promise<sourcegraph.Location[] | null> {
if (!this.enabled) {
return null
}

const lines = doc.text.split('\n')
const line = lines[pos.line]
let end = line.length
Expand Down
17 changes: 12 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5082,6 +5082,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"

rxjs@^6.3.3:
version "6.3.3"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==
dependencies:
tslib "^1.9.0"

safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
Expand Down Expand Up @@ -5306,10 +5313,10 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=

sourcegraph@^19.2.0:
version "19.2.0"
resolved "https://registry.yarnpkg.com/sourcegraph/-/sourcegraph-19.2.0.tgz#bcb6bc14ec756981e36f616cff55d8e34169b9c8"
integrity sha512-mGoCuJngYCT2DOiHLY9/JQrg2Me73dpgtLUlCYOTZjhYg8g0B4XRmde1RLIX+Geb5WM1/0d5IlyV1gVfCe5aYw==
sourcegraph@^19.3.0:
version "19.3.0"
resolved "https://registry.yarnpkg.com/sourcegraph/-/sourcegraph-19.3.0.tgz#5133f7107880821a54ca0339ecfc49ab02a237ab"
integrity sha512-E93URCdzQXmF6goxDg6Uml55wJZwW843oPr59lapYilaYkMiTHBZup7IAZdWuCvvXe9CN1D7q9pm66aVY5QfLA==

spawn-wrap@^1.4.2:
version "1.4.2"
Expand Down Expand Up @@ -5697,7 +5704,7 @@ ts-node@^7.0.1:
source-map-support "^0.5.6"
yn "^2.0.0"

tslib@^1.8.0, tslib@^1.8.1:
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
Expand Down