Skip to content

Commit 9a2e863

Browse files
committed
actually deregister the inline completion provider when disabled
1 parent bd4f19d commit 9a2e863

File tree

3 files changed

+128
-11
lines changed

3 files changed

+128
-11
lines changed

src/services/ghost/GhostServiceManager.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,21 @@ export class GhostServiceManager {
121121
await this.saveSettings()
122122
}
123123

124-
/**
125-
* Update the inline completion provider with current settings
126-
*/
127124
private async updateInlineCompletionProviderRegistration() {
128-
// Always keep the provider registered so manual triggers (cmd-L) work
129-
// The provider will check enableAutoTrigger internally to decide whether to auto-trigger
130-
if (!this.inlineCompletionProviderDisposable) {
125+
const shouldBeRegistered = this.settings?.enableAutoTrigger ?? false
126+
127+
if (shouldBeRegistered && !this.inlineCompletionProviderDisposable) {
128+
// Register the provider
131129
this.inlineCompletionProviderDisposable = vscode.languages.registerInlineCompletionItemProvider(
132130
"*",
133131
this.inlineCompletionProvider,
134132
)
135133
this.context.subscriptions.push(this.inlineCompletionProviderDisposable)
134+
} else if (!shouldBeRegistered && this.inlineCompletionProviderDisposable) {
135+
// Deregister the provider
136+
this.inlineCompletionProviderDisposable.dispose()
137+
this.inlineCompletionProviderDisposable = null
136138
}
137-
// No need to update settings - provider reads them dynamically via getSettings callback
138139
}
139140

140141
public async disable() {

src/services/ghost/__tests__/GhostServiceManager.spec.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,113 @@ console.log('test');]]></replace></change>`
290290
expect(mockProvider.provideInlineCompletionItems).not.toHaveBeenCalled()
291291
})
292292
})
293+
294+
describe("updateInlineCompletionProviderRegistration", () => {
295+
it("should register provider when enableAutoTrigger is true", async () => {
296+
const mockDisposable = { dispose: vi.fn() }
297+
const mockRegister = vi.fn().mockReturnValue(mockDisposable)
298+
299+
const mockManager = {
300+
settings: { enableAutoTrigger: true } as any,
301+
inlineCompletionProviderDisposable: null as any,
302+
inlineCompletionProvider: {} as any,
303+
context: { subscriptions: [] as any[] },
304+
async updateInlineCompletionProviderRegistration() {
305+
const shouldBeRegistered = this.settings?.enableAutoTrigger ?? false
306+
307+
if (shouldBeRegistered && !this.inlineCompletionProviderDisposable) {
308+
this.inlineCompletionProviderDisposable = mockRegister("*", this.inlineCompletionProvider)
309+
this.context.subscriptions.push(this.inlineCompletionProviderDisposable)
310+
} else if (!shouldBeRegistered && this.inlineCompletionProviderDisposable) {
311+
this.inlineCompletionProviderDisposable.dispose()
312+
this.inlineCompletionProviderDisposable = null
313+
}
314+
},
315+
}
316+
317+
await mockManager.updateInlineCompletionProviderRegistration()
318+
319+
expect(mockRegister).toHaveBeenCalledWith("*", mockManager.inlineCompletionProvider)
320+
expect(mockManager.inlineCompletionProviderDisposable).toBe(mockDisposable)
321+
expect(mockManager.context.subscriptions).toContain(mockDisposable)
322+
})
323+
324+
it("should deregister provider when enableAutoTrigger is false", async () => {
325+
const mockDisposable = { dispose: vi.fn() }
326+
327+
const mockManager = {
328+
settings: { enableAutoTrigger: false } as any,
329+
inlineCompletionProviderDisposable: mockDisposable as any,
330+
inlineCompletionProvider: {} as any,
331+
context: { subscriptions: [mockDisposable] as any[] },
332+
async updateInlineCompletionProviderRegistration() {
333+
const shouldBeRegistered = this.settings?.enableAutoTrigger ?? false
334+
335+
if (shouldBeRegistered && !this.inlineCompletionProviderDisposable) {
336+
// Register logic (not executed in this test)
337+
} else if (!shouldBeRegistered && this.inlineCompletionProviderDisposable) {
338+
this.inlineCompletionProviderDisposable.dispose()
339+
this.inlineCompletionProviderDisposable = null
340+
}
341+
},
342+
}
343+
344+
await mockManager.updateInlineCompletionProviderRegistration()
345+
346+
expect(mockDisposable.dispose).toHaveBeenCalled()
347+
expect(mockManager.inlineCompletionProviderDisposable).toBeNull()
348+
})
349+
350+
it("should not register provider twice when already registered", async () => {
351+
const mockDisposable = { dispose: vi.fn() }
352+
const mockRegister = vi.fn().mockReturnValue(mockDisposable)
353+
354+
const mockManager = {
355+
settings: { enableAutoTrigger: true } as any,
356+
inlineCompletionProviderDisposable: mockDisposable as any,
357+
inlineCompletionProvider: {} as any,
358+
context: { subscriptions: [mockDisposable] as any[] },
359+
async updateInlineCompletionProviderRegistration() {
360+
const shouldBeRegistered = this.settings?.enableAutoTrigger ?? false
361+
362+
if (shouldBeRegistered && !this.inlineCompletionProviderDisposable) {
363+
this.inlineCompletionProviderDisposable = mockRegister("*", this.inlineCompletionProvider)
364+
this.context.subscriptions.push(this.inlineCompletionProviderDisposable)
365+
} else if (!shouldBeRegistered && this.inlineCompletionProviderDisposable) {
366+
this.inlineCompletionProviderDisposable.dispose()
367+
this.inlineCompletionProviderDisposable = null
368+
}
369+
},
370+
}
371+
372+
await mockManager.updateInlineCompletionProviderRegistration()
373+
374+
expect(mockRegister).not.toHaveBeenCalled()
375+
expect(mockManager.inlineCompletionProviderDisposable).toBe(mockDisposable)
376+
})
377+
378+
it("should not deregister when already deregistered", async () => {
379+
const mockManager = {
380+
settings: { enableAutoTrigger: false } as any,
381+
inlineCompletionProviderDisposable: null as any,
382+
inlineCompletionProvider: {} as any,
383+
context: { subscriptions: [] as any[] },
384+
async updateInlineCompletionProviderRegistration() {
385+
const shouldBeRegistered = this.settings?.enableAutoTrigger ?? false
386+
387+
if (shouldBeRegistered && !this.inlineCompletionProviderDisposable) {
388+
// Register logic (not executed in this test)
389+
} else if (!shouldBeRegistered && this.inlineCompletionProviderDisposable) {
390+
this.inlineCompletionProviderDisposable.dispose()
391+
this.inlineCompletionProviderDisposable = null
392+
}
393+
},
394+
}
395+
396+
// Should not throw or cause issues
397+
await mockManager.updateInlineCompletionProviderRegistration()
398+
399+
expect(mockManager.inlineCompletionProviderDisposable).toBeNull()
400+
})
401+
})
293402
})

src/services/ghost/classic-auto-complete/__tests__/GhostInlineCompletionProvider.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ describe("GhostInlineCompletionProvider", () => {
922922
expect(mockModel.generateResponse).not.toHaveBeenCalled()
923923
})
924924

925-
it("should allow manual trigger even when auto-trigger is disabled", async () => {
925+
it("should block manual trigger when auto-trigger is disabled (defense in depth)", async () => {
926926
// Set auto-trigger to false
927927
mockSettings = { enableAutoTrigger: false }
928928

@@ -932,10 +932,17 @@ describe("GhostInlineCompletionProvider", () => {
932932
selectedCompletionInfo: undefined,
933933
} as vscode.InlineCompletionContext
934934

935-
await provider.provideInlineCompletionItems(mockDocument, mockPosition, manualContext, mockToken)
935+
const result = await provider.provideInlineCompletionItems(
936+
mockDocument,
937+
mockPosition,
938+
manualContext,
939+
mockToken,
940+
)
936941

937-
// Model should be called even though auto-trigger is disabled
938-
expect(mockModel.generateResponse).toHaveBeenCalled()
942+
// Should return empty array as defense in depth, even for manual triggers
943+
// The provider should be deregistered at the manager level when disabled
944+
expect(result).toEqual([])
945+
expect(mockModel.generateResponse).not.toHaveBeenCalled()
939946
})
940947

941948
it("should read settings dynamically on each call", async () => {

0 commit comments

Comments
 (0)