diff --git a/src/extension/completions-core/vscode-node/extension/src/config.ts b/src/extension/completions-core/vscode-node/extension/src/config.ts index af0aef056a..02def86645 100644 --- a/src/extension/completions-core/vscode-node/extension/src/config.ts +++ b/src/extension/completions-core/vscode-node/extension/src/config.ts @@ -148,7 +148,26 @@ export function isCompletionEnabled(accessor: ServicesAccessor): boolean | undef return isCompletionEnabledForDocument(accessor, editor.document); } +// --- Start Positron --- +const PositronInlineCompletionsEnableConfigKey = 'positron.assistant.inlineCompletions.enable'; +const PositronInlineCompletionsEnableDefault: { [key: string]: boolean } = { '*': true }; + +function isPositronCompletionEnabledForLanguage(languageId: string): boolean { + const enabledLanguages = vscode.workspace.getConfiguration().get<{ [key: string]: boolean }>(PositronInlineCompletionsEnableConfigKey) ?? PositronInlineCompletionsEnableDefault; + const enabledLanguagesMap = new Map(Object.entries(enabledLanguages)); + if (!enabledLanguagesMap.has('*')) { + enabledLanguagesMap.set('*', false); + } + return enabledLanguagesMap.has(languageId) ? enabledLanguagesMap.get(languageId)! : enabledLanguagesMap.get('*')!; +} +// --- End Positron --- + export function isCompletionEnabledForDocument(accessor: ServicesAccessor, document: vscode.TextDocument): boolean { + // --- Start Positron --- + if (!isPositronCompletionEnabledForLanguage(document.languageId)) { + return false; + } + // --- End Positron --- return getEnabledConfig(accessor, document.languageId); } diff --git a/src/extension/completions/vscode-node/completionsCoreContribution.ts b/src/extension/completions/vscode-node/completionsCoreContribution.ts index 41c42f8eb6..39fd580ac7 100644 --- a/src/extension/completions/vscode-node/completionsCoreContribution.ts +++ b/src/extension/completions/vscode-node/completionsCoreContribution.ts @@ -35,11 +35,21 @@ export class CompletionsCoreContribution extends Disposable { this._register(autorun(reader => { const unificationStateValue = unificationState.read(reader); const configEnabled = configurationService.getExperimentBasedConfigObservable(ConfigKey.Internal.InlineEditsEnableGhCompletionsProvider, experimentationService).read(reader); - const extensionUnification = unificationStateValue?.extensionUnification ?? false; + // --- Start Positron --- + // Always enable extension unification for Positron; we do not have + // access to the proprietary GitHub Copilot extension that would + // otherwise provide completions. + // + // const extensionUnification = unificationStateValue?.extensionUnification ?? false; + const extensionUnification = true; + // --- End Positron --- - if (unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser) { + if (unificationStateValue?.codeUnification || extensionUnification || configEnabled || this._copilotToken.read(reader)?.isNoAuthUser || Math.random() > 0) { const provider = this._getOrCreateProvider(); - reader.store.add(languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider, { debounceDelayMs: 0, excludes: ['github.copilot'], groupId: 'completions' })); + // --- Start Positron --- + // Added displayName property for showing in Completion Providers + reader.store.add(languages.registerInlineCompletionItemProvider({ pattern: '**' }, provider, { displayName: 'GitHub Copilot', debounceDelayMs: 0, excludes: ['github.copilot'], groupId: 'completions' })); + // --- End Positron --- } void commands.executeCommand('setContext', 'github.copilot.extensionUnification.activated', extensionUnification); diff --git a/src/extension/extension/vscode-node/extension.ts b/src/extension/extension/vscode-node/extension.ts index 3a71485bf2..467285e8de 100644 --- a/src/extension/extension/vscode-node/extension.ts +++ b/src/extension/extension/vscode-node/extension.ts @@ -22,7 +22,6 @@ import '../../intents/node/allIntents'; // --- Start Positron --- import * as vscode from 'vscode'; -import { getFileBasedAuthSession } from '../../../platform/authentication/vscode-node/fileBasedAuth.js'; // --- End Positron --- function configureDevPackages() { @@ -48,28 +47,7 @@ export function activate(context: ExtensionContext, forceActivation?: boolean) { return; } - // Don't perform activation if we have no auth session; the original - // extension has an "installed but not signed in" state, but we don't - // support that in Positron. - const authSession = getFileBasedAuthSession(); - if (!authSession) { - // There's no auth session yet, but we don't want to require a restart - // when one is established. Listen for a Copilot auth session to be - // established. - console.log(`[Copilot Chat] No auth session found, extension will not activate until sign-in`); - const api = vscode.extensions.getExtension('positron.positron-assistant')?.exports; - if (api) { - api.onProviderSignIn((provider: string) => { - if (provider === 'copilot') { - console.info('[Copilot Chat] Detected Copilot sign-in, activating extension'); - activate(context, forceActivation); - } - }); - } else { - console.error('[Copilot Chat] Failed to get Positron API'); - } - return; - } + // TODO: Don't activate until we have an auth session // --- End Positron --- return baseActivate({ diff --git a/src/extension/inlineEdits/common/positronConfig.ts b/src/extension/inlineEdits/common/positronConfig.ts new file mode 100644 index 0000000000..1cf5f5a707 --- /dev/null +++ b/src/extension/inlineEdits/common/positronConfig.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (C) 2025 Posit Software, PBC. All rights reserved. + * Licensed under the Elastic License 2.0. See LICENSE.txt for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Configuration key for Positron inline completions enable setting. + * This has the same format as github.copilot.enable: { [languageId]: boolean } + */ +export const PositronInlineCompletionsEnableConfigKey = 'positron.assistant.inlineCompletions.enable'; + +/** + * Default value for the Positron inline completions enable setting. + */ +export const PositronInlineCompletionsEnableDefault: { [key: string]: boolean } = { + '*': true, +}; diff --git a/src/extension/inlineEdits/vscode-node/inlineCompletionProvider.ts b/src/extension/inlineEdits/vscode-node/inlineCompletionProvider.ts index d194093074..bbab2e9bf8 100644 --- a/src/extension/inlineEdits/vscode-node/inlineCompletionProvider.ts +++ b/src/extension/inlineEdits/vscode-node/inlineCompletionProvider.ts @@ -39,6 +39,10 @@ import { InlineEditLogger } from './parts/inlineEditLogger'; import { IVSCodeObservableDocument } from './parts/vscodeWorkspace'; import { toExternalRange } from './utils/translations'; +// --- Start Positron --- +import { PositronInlineCompletionsEnableConfigKey, PositronInlineCompletionsEnableDefault } from '../common/positronConfig'; +// --- End Positron --- + const learnMoreAction: Command = { title: l10n.t('Learn More'), command: learnMoreCommandId, @@ -122,16 +126,18 @@ export class InlineCompletionProviderImpl implements InlineCompletionItemProvide this._tracer = createTracer(['NES', 'Provider'], (s) => this._logService.trace(s)); this._displayNextEditorNES = this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.UseAlternativeNESNotebookFormat, this._expService); } - + // --- Start Positron --- + // Use Positron's inline completions enable config key instead of Copilot's // copied from `vscodeWorkspace.ts` `DocumentFilter#_enabledLanguages` private _isCompletionsEnabled(document: TextDocument): boolean { - const enabledLanguages = this._configurationService.getConfig(ConfigKey.Enable); + const enabledLanguages = this._configurationService.getNonExtensionConfig<{ [key: string]: boolean }>(PositronInlineCompletionsEnableConfigKey) ?? PositronInlineCompletionsEnableDefault; const enabledLanguagesMap = new Map(Object.entries(enabledLanguages)); if (!enabledLanguagesMap.has('*')) { enabledLanguagesMap.set('*', false); } return enabledLanguagesMap.has(document.languageId) ? enabledLanguagesMap.get(document.languageId)! : enabledLanguagesMap.get('*')!; } + // --- End Positron --- public async provideInlineCompletionItems( document: TextDocument, @@ -141,7 +147,15 @@ export class InlineCompletionProviderImpl implements InlineCompletionItemProvide ): Promise { const tracer = this._tracer.sub(['provideInlineCompletionItems', shortOpportunityId(context.requestUuid)]); + // --- Start Positron --- + // If inline completions are disabled for this language, don't + // provide any completions. const isCompletionsEnabled = this._isCompletionsEnabled(document); + if (!isCompletionsEnabled) { + tracer.returns('inline completions disabled for this language'); + return undefined; + } + // --- End Positron --- const unification = this._configurationService.getExperimentBasedConfig(ConfigKey.Internal.InlineEditsUnification, this._expService); diff --git a/src/extension/inlineEdits/vscode-node/parts/documentFilter.ts b/src/extension/inlineEdits/vscode-node/parts/documentFilter.ts index a081a830e4..252fb41ab0 100644 --- a/src/extension/inlineEdits/vscode-node/parts/documentFilter.ts +++ b/src/extension/inlineEdits/vscode-node/parts/documentFilter.ts @@ -9,6 +9,10 @@ import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService import { isNotebookCellOrNotebookChatInput } from '../../../../util/common/notebooks'; import { derived } from '../../../../util/vs/base/common/observableInternal'; +// --- Start Positron --- +import { PositronInlineCompletionsEnableConfigKey, PositronInlineCompletionsEnableDefault } from '../../common/positronConfig'; +// --- End Positron --- + export class DocumentFilter { private readonly _enabledLanguagesObs; private readonly _ignoreCompletionsDisablement; @@ -17,7 +21,13 @@ export class DocumentFilter { @IIgnoreService private readonly _ignoreService: IIgnoreService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { - this._enabledLanguagesObs = this._configurationService.getConfigObservable(ConfigKey.Enable); + // --- Start Positron --- + // Use Positron's inline completions enable config key instead of Copilot's + this._enabledLanguagesObs = this._configurationService.getNonExtensionConfigObservable<{ [key: string]: boolean }>( + PositronInlineCompletionsEnableConfigKey, + PositronInlineCompletionsEnableDefault + ); + // --- End Positron --- this._ignoreCompletionsDisablement = this._configurationService.getConfigObservable(ConfigKey.Internal.InlineEditsIgnoreCompletionsDisablement); } diff --git a/src/platform/authentication/vscode-node/session.ts b/src/platform/authentication/vscode-node/session.ts index 72257c27a3..66b49cedbe 100644 --- a/src/platform/authentication/vscode-node/session.ts +++ b/src/platform/authentication/vscode-node/session.ts @@ -9,10 +9,6 @@ import { AuthPermissionMode, AuthProviderId, ConfigKey, IConfigurationService } import { GITHUB_SCOPE_ALIGNED, GITHUB_SCOPE_READ_USER, GITHUB_SCOPE_USER_EMAIL, MinimalModeError } from '../common/authentication'; import { mixin } from '../../../util/vs/base/common/objects'; -// --- Start Positron --- -import { getFileBasedAuthSession } from './fileBasedAuth'; -// --- End Positron --- - export const SESSION_LOGIN_MESSAGE = 'You are not signed in to GitHub. Please sign in to use Copilot.'; // These types are subsets of the "real" types AuthenticationSessionAccountInformation and // AuthenticationSession. They allow us to use the type system to validate which fields @@ -73,15 +69,6 @@ async function getAuthSession(providerId: string, defaultScopes: string[], getSi * @deprecated use `IAuthenticationService` instead */ export function getAnyAuthSession(configurationService: IConfigurationService, options?: AuthenticationGetSessionOptions): Promise { - // --- Start Positron --- - // First, try to get authentication from the file-based GitHub Copilot apps.json - const fileBasedSession = getFileBasedAuthSession(); - if (fileBasedSession) { - return Promise.resolve(fileBasedSession); - } - - // Fall back to VS Code authentication system - // --- End Positron --- const providerId = authProviderId(configurationService); diff --git a/src/platform/configuration/common/configurationService.ts b/src/platform/configuration/common/configurationService.ts index ad22793ac7..e0fd7282e4 100644 --- a/src/platform/configuration/common/configurationService.ts +++ b/src/platform/configuration/common/configurationService.ts @@ -117,6 +117,16 @@ export interface IConfigurationService { */ getNonExtensionConfig(configKey: string): T | undefined; + // --- Start Positron --- + + /** + * Gets an observable for a configuration value that is not in the Copilot namespace. + * @param configKey The fully qualified config key to look up (e.g., 'positron.assistant.inlineCompletions.enable') + * @param defaultValue The default value to use if the config is not set + */ + getNonExtensionConfigObservable(configKey: string, defaultValue: T): IObservable; + // --- End Positron --- + /** * Sets user configuration for a key in vscode. */ @@ -260,6 +270,29 @@ export abstract class AbstractConfigurationService extends Disposable implements private observables = new Map>(); + // --- Start Positron --- + public getNonExtensionConfigObservable(configKey: string, defaultValue: T): IObservable { + return this._getNonExtensionObservable(configKey, defaultValue); + } + + private _getNonExtensionObservable(configKey: string, defaultValue: T): IObservable { + let observable = this.observables.get(configKey); + if (!observable) { + observable = observableFromEventOpts( + { debugName: () => `Non-Extension Configuration Key "${configKey}"` }, + (handleChange) => this._register(this.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(configKey)) { + handleChange(e); + } + })), + () => this.getNonExtensionConfig(configKey) ?? defaultValue + ); + this.observables.set(configKey, observable); + } + return observable; + } + // --- End Positron --- + private _getObservable_$show2FramesUp(key: BaseConfig, getValue: () => T): IObservable { let observable = this.observables.get(key.id); if (!observable) { @@ -739,6 +772,9 @@ export namespace ConfigKey { export const Gpt5AlternativePatch = defineExpSetting('chat.advanced.gpt5AlternativePatch', false); } + // --- Start Positron --- + // Note: Not used in Positron, but kept here to avoid breaking changes + // --- End Positron --- export const Enable = defineSetting<{ [key: string]: boolean }>('enable', { "*": true, "plaintext": false, diff --git a/src/platform/configuration/test/common/inMemoryConfigurationService.ts b/src/platform/configuration/test/common/inMemoryConfigurationService.ts index 45f19c3632..a3eededb6d 100644 --- a/src/platform/configuration/test/common/inMemoryConfigurationService.ts +++ b/src/platform/configuration/test/common/inMemoryConfigurationService.ts @@ -46,11 +46,17 @@ export class InMemoryConfigurationService extends AbstractConfigurationService { override setConfig(key: BaseConfig, value: T): Promise { this.overrides.set(key, value); + // --- Start Positron --- + this._onDidChangeConfiguration.fire({ affectsConfiguration: (k) => k === key.fullyQualifiedId }); + // --- End Positron --- return Promise.resolve(); } setNonExtensionConfig(key: string, value: T): Promise { this.nonExtensionOverrides.set(key, value); + // --- Start Positron --- + this._onDidChangeConfiguration.fire({ affectsConfiguration: (k) => k === key }); + // --- End Positron --- return Promise.resolve(); }