diff --git a/package.json b/package.json index 27982d9a1..16c6369d3 100644 --- a/package.json +++ b/package.json @@ -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\"}", @@ -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" } ] }, @@ -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": { @@ -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 } diff --git a/src/api.ts b/src/api.ts index 5d85a46e0..31b063a51 100644 --- a/src/api.ts +++ b/src/api.ts @@ -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. @@ -22,7 +22,7 @@ export class API { private get traceSearch(): boolean { return Boolean( sourcegraph.configuration - .get() + .get() .get('basicCodeIntel.debug.traceSearch') ) } diff --git a/src/extension.ts b/src/extension.ts index f09cf068c..31781a89f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -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 { +// 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(sub => { + sub.next(sourcegraph.configuration.get().value) + return sourcegraph.configuration.subscribe(() => + sub.next(sourcegraph.configuration.get().value) + ) +}) + +function enabledOrNull(provider: () => T): T | null { + if ( + !sourcegraph.configuration.get().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( + result: Observable | Promise +): sourcegraph.ProviderResult { + // HACK: Earlier extension API versions did not support providers returning observables. We can detect whether + // 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() } diff --git a/src/handler.ts b/src/handler.ts index 36ebcb45c..244021c76 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -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 */ @@ -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 } @@ -139,23 +146,16 @@ export class Handler { */ public api = new API() - private get enabled(): boolean { - return Boolean( - sourcegraph.configuration - .get() - .get('basicCodeIntel.enabled') - ) - } - async definition( doc: sourcegraph.TextDocument, pos: sourcegraph.Position, symbols = sourcegraph.configuration - .get() + .get() .get('basicCodeIntel.definition.symbols') - ): Promise { - if (!this.enabled) { - return null + ): Promise { + // Default to using local symbols lookup. + if (!symbols) { + symbols = 'local' } const lines = doc.text.split('\n') @@ -208,10 +208,6 @@ export class Handler { doc: sourcegraph.TextDocument, pos: sourcegraph.Position ): Promise { - if (!this.enabled) { - return null - } - const lines = doc.text.split('\n') const line = lines[pos.line] let end = line.length diff --git a/yarn.lock b/yarn.lock index d71d3a6b6..95459ebca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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==