From ca031bea9ffba63c077ed3b5e3da54afaf2d3667 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Mon, 13 Apr 2026 20:54:26 +0500 Subject: [PATCH] NES: enable trigger on active editor change by default (#309489) * NES: enable trigger on active editor change by default - Set triggerOnEditorChangeAfterSeconds default to 10 (was undefined) - Set triggerOnEditorChangeStrategy default to AfterAcceptance (was Always) - Replace unconditional same-line cooldown bypass with smart reset: cooldown is enforced within a file session but clears when switching away and returning, so NES re-triggers on the same line after a round-trip to another file - Update tests to match new defaults and behavior * fix indentation --- extensions/copilot/package.json | 1 + .../vscode-node/inlineEditTriggerer.spec.ts | 34 ++++++++++++------- .../vscode-node/inlineEditTriggerer.ts | 21 +++++++----- .../common/configurationService.ts | 4 +-- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/extensions/copilot/package.json b/extensions/copilot/package.json index b00e2f1d0a6b9..74e73d9ca76d3 100644 --- a/extensions/copilot/package.json +++ b/extensions/copilot/package.json @@ -4435,6 +4435,7 @@ "number", "null" ], + "default": 10, "markdownDescription": "%github.copilot.config.inlineEdits.triggerOnEditorChangeAfterSeconds%", "tags": [ "advanced", diff --git a/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts b/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts index 2193f7f6ee84e..972253a394ff0 100644 --- a/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts +++ b/extensions/copilot/src/extension/inlineEdits/test/vscode-node/inlineEditTriggerer.spec.ts @@ -392,6 +392,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; // Configure to trigger on document switch void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); @@ -504,6 +505,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; const triggerAfterSeconds = 30; // Configure to trigger on document switch @@ -930,6 +932,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py'), 'line1\nline2'); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); // Edit doc1 and trigger @@ -1148,6 +1151,7 @@ suite('InlineEditTriggerer', () => { const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; + nextEditProvider.lastOutcome = NesOutcome.Accepted; void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); // Fire an undo change — this should still update lastEditTimestamp @@ -1216,25 +1220,31 @@ suite('InlineEditTriggerer', () => { assert.strictEqual(firedEvents.length, 0, 'Should not fire on doc switch during rejection cooldown'); }); - test('Same-line cooldown is bypassed when triggerOnActiveEditorChange is set', () => { - const { document, textEditor } = createTextDocument(); + test('Same-line cooldown is bypassed after switching away and back', () => { + const doc1 = createTextDocument(undefined, Uri.file('file1.py')); + const doc2 = createTextDocument(undefined, Uri.file('file2.py')); nextEditProvider.lastRejectionTime = Date.now() - TRIGGER_INLINE_EDIT_REJECTION_COOLDOWN - 1; - // Enable triggerOnActiveEditorChange — this bypasses same-line cooldown - void configurationService.setConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, 30); - - triggerTextChange(document); - triggerTextSelectionChange(textEditor, new Selection(0, 5, 0, 5)); + // Edit doc1 and trigger on line 0 + triggerTextChange(doc1.document); + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 5, 0, 5)); const initialCount = firedEvents.length; assert.isAtLeast(initialCount, 1, 'First trigger should fire'); - // Same line, different column — normally would be in cooldown, - // but triggerOnActiveEditorChange is set so cooldown is bypassed - triggerTextSelectionChange(textEditor, new Selection(0, 10, 0, 10)); + // Same line — cooldown blocks + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 10, 0, 10)); + assert.strictEqual(firedEvents.length, initialCount, 'Same-line cooldown should block'); + + // Switch to doc2 + triggerTextChange(doc2.document); + triggerTextSelectionChange(doc2.textEditor, new Selection(0, 0, 0, 0)); + const countAfterDoc2 = firedEvents.length; - assert.isAtLeast(firedEvents.length, initialCount + 1, - 'Same-line cooldown should be bypassed when triggerOnActiveEditorChange is set'); + // Switch back to doc1, same line — cooldown should be cleared by the doc switch + triggerTextSelectionChange(doc1.textEditor, new Selection(0, 10, 0, 10)); + assert.isAtLeast(firedEvents.length, countAfterDoc2 + 1, + 'Same-line cooldown should be bypassed after switching away and back'); }); test('Output pane documents are ignored for selection changes', () => { diff --git a/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts b/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts index 9eaf836c1d3f9..b50da76660495 100644 --- a/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts +++ b/extensions/copilot/src/extension/inlineEdits/vscode-node/inlineEditTriggerer.ts @@ -157,6 +157,12 @@ export class InlineEditTriggerer extends Disposable { return; } + // When the user switches to a different file and comes back, clear same-line + // cooldowns so the triggerer fires again on the same line. + if (!isSameDoc) { + mostRecentChange.lineNumberTriggers.clear(); + } + const hadRecentEdit = this._hasRecentEdit(mostRecentChange); if (!hadRecentEdit || !this._hasRecentTrigger()) { // The edit is too old or the provider was not triggered recently (we might be @@ -221,17 +227,14 @@ export class InlineEditTriggerer extends Disposable { /** * Returns true if the same-line cooldown is active and we should skip triggering. * - * The cooldown is bypassed when: - * - `triggerOnActiveEditorChange` is configured, OR - * - we're in a notebook cell and the current document differs from the one that - * originally triggered the change (user moved to a different cell). + * The cooldown is bypassed when we're in a notebook cell and the current document + * differs from the one that originally triggered the change (user moved to a + * different cell). + * + * When the user switches to a different file and comes back, line triggers are + * cleared (see {@link _handleSelectionChange}), so the cooldown naturally resets. */ private _isSameLineCooldownActive(mostRecentChange: LastChange, selectionLine: number, currentDocument: vscode.TextDocument): boolean { - const triggerOnActiveEditorChange = this._configurationService.getExperimentBasedConfig(ConfigKey.Advanced.InlineEditsTriggerOnEditorChangeAfterSeconds, this._expService); - if (triggerOnActiveEditorChange) { - return false; // cooldown bypassed - } - // In a notebook, if the user moved to a different cell, bypass the cooldown if (isNotebookCell(currentDocument.uri) && currentDocument !== mostRecentChange.documentTrigger) { return false; // cooldown bypassed diff --git a/extensions/copilot/src/platform/configuration/common/configurationService.ts b/extensions/copilot/src/platform/configuration/common/configurationService.ts index 93360356e49fd..5859379d82839 100644 --- a/extensions/copilot/src/platform/configuration/common/configurationService.ts +++ b/extensions/copilot/src/platform/configuration/common/configurationService.ts @@ -659,7 +659,7 @@ export namespace ConfigKey { /** Maximum number of tool calls the execution subagent can make */ export const ExecutionSubagentToolCallLimit = defineSetting('chat.executionSubagent.toolCallLimit', ConfigType.ExperimentBased, 10); - export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', undefined); + export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', 10); export const InlineEditsNextCursorPredictionDisplayLine = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.displayLine', 'chat.inlineEdits.nextCursorPrediction.displayLine', true); export const InlineEditsNextCursorPredictionCurrentFileMaxTokens = defineAndMigrateExpSetting('chat.advanced.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 'chat.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 3000); export const InlineEditsRenameSymbolSuggestions = defineSetting('chat.inlineEdits.renameSymbolSuggestions', ConfigType.ExperimentBased, true); @@ -777,7 +777,7 @@ export namespace ConfigKey { export const InlineEditsSpeculativeRequestsAutoExpandEditWindowLines = defineTeamInternalSetting('chat.advanced.inlineEdits.speculativeRequestsAutoExpandEditWindowLines', ConfigType.ExperimentBased, SpeculativeRequestsAutoExpandEditWindowLines.Off, SpeculativeRequestsAutoExpandEditWindowLines.VALIDATOR); export const InlineEditsExtraDebounceInlineSuggestion = defineTeamInternalSetting('chat.advanced.inlineEdits.extraDebounceInlineSuggestion', ConfigType.ExperimentBased, 0); export const InlineEditsDebounceOnSelectionChange = defineTeamInternalSetting('chat.advanced.inlineEdits.debounceOnSelectionChange', ConfigType.ExperimentBased, undefined); - export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.Always, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR); + export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.AfterAcceptance, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR); export const InlineEditsProviderId = defineTeamInternalSetting('chat.advanced.inlineEdits.providerId', ConfigType.ExperimentBased, undefined); export const InlineEditsUnification = defineTeamInternalSetting('chat.advanced.inlineEdits.unification', ConfigType.ExperimentBased, false); export const InlineEditsNextCursorPredictionModelName = defineTeamInternalSetting('chat.advanced.inlineEdits.nextCursorPrediction.modelName', ConfigType.ExperimentBased, 'copilot-suggestions-himalia-001');