Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions extensions/copilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4428,6 +4428,7 @@
"number",
"null"
],
"default": 10,
"markdownDescription": "%github.copilot.config.inlineEdits.triggerOnEditorChangeAfterSeconds%",
"tags": [
"advanced",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,7 @@ export namespace ConfigKey {
/** Maximum number of tool calls the execution subagent can make */
export const ExecutionSubagentToolCallLimit = defineSetting<number>('chat.executionSubagent.toolCallLimit', ConfigType.ExperimentBased, 10);

export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting<number | undefined>('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', undefined);
export const InlineEditsTriggerOnEditorChangeAfterSeconds = defineAndMigrateExpSetting<number | undefined>('chat.advanced.inlineEdits.triggerOnEditorChangeAfterSeconds', 'chat.inlineEdits.triggerOnEditorChangeAfterSeconds', 10);
export const InlineEditsNextCursorPredictionDisplayLine = defineAndMigrateExpSetting<boolean>('chat.advanced.inlineEdits.nextCursorPrediction.displayLine', 'chat.inlineEdits.nextCursorPrediction.displayLine', true);
export const InlineEditsNextCursorPredictionCurrentFileMaxTokens = defineAndMigrateExpSetting<number>('chat.advanced.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 'chat.inlineEdits.nextCursorPrediction.currentFileMaxTokens', 3000);
export const InlineEditsRenameSymbolSuggestions = defineSetting<boolean>('chat.inlineEdits.renameSymbolSuggestions', ConfigType.ExperimentBased, true);
Expand Down Expand Up @@ -777,7 +777,7 @@ export namespace ConfigKey {
export const InlineEditsSpeculativeRequestsAutoExpandEditWindowLines = defineTeamInternalSetting<SpeculativeRequestsAutoExpandEditWindowLines>('chat.advanced.inlineEdits.speculativeRequestsAutoExpandEditWindowLines', ConfigType.ExperimentBased, SpeculativeRequestsAutoExpandEditWindowLines.Off, SpeculativeRequestsAutoExpandEditWindowLines.VALIDATOR);
export const InlineEditsExtraDebounceInlineSuggestion = defineTeamInternalSetting<number>('chat.advanced.inlineEdits.extraDebounceInlineSuggestion', ConfigType.ExperimentBased, 0);
export const InlineEditsDebounceOnSelectionChange = defineTeamInternalSetting<number | undefined>('chat.advanced.inlineEdits.debounceOnSelectionChange', ConfigType.ExperimentBased, undefined);
export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting<triggerOptions.DocumentSwitchTriggerStrategy>('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.Always, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR);
export const InlineEditsTriggerOnEditorChangeStrategy = defineTeamInternalSetting<triggerOptions.DocumentSwitchTriggerStrategy>('chat.advanced.inlineEdits.triggerOnEditorChangeStrategy', ConfigType.ExperimentBased, triggerOptions.DocumentSwitchTriggerStrategy.AfterAcceptance, triggerOptions.DocumentSwitchTriggerStrategy.VALIDATOR);
export const InlineEditsProviderId = defineTeamInternalSetting<string | undefined>('chat.advanced.inlineEdits.providerId', ConfigType.ExperimentBased, undefined);
export const InlineEditsUnification = defineTeamInternalSetting<boolean>('chat.advanced.inlineEdits.unification', ConfigType.ExperimentBased, false);
export const InlineEditsNextCursorPredictionModelName = defineTeamInternalSetting<string | undefined>('chat.advanced.inlineEdits.nextCursorPrediction.modelName', ConfigType.ExperimentBased, 'copilot-suggestions-himalia-001');
Expand Down
Loading