Skip to content

Commit 92669c0

Browse files
authored
Chat modes not updated on model change & contribution update (microsoft#269554)
* Chat mode files not updated when autosave is off * update * fix
1 parent fa7008e commit 92669c0

1 file changed

Lines changed: 74 additions & 14 deletions

File tree

src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { localize } from '../../../../../../nls.js';
7-
import { getPromptsTypeForLanguageId, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js';
7+
import { getPromptsTypeForLanguageId, MODE_LANGUAGE_ID, PROMPT_LANGUAGE_ID, PromptsType } from '../promptTypes.js';
88
import { type URI } from '../../../../../../base/common/uri.js';
99
import { basename } from '../../../../../../base/common/path.js';
1010
import { PromptFilesLocator } from '../utils/promptFilesLocator.js';
11-
import { Disposable } from '../../../../../../base/common/lifecycle.js';
12-
import { Event } from '../../../../../../base/common/event.js';
11+
import { Disposable, IDisposable } from '../../../../../../base/common/lifecycle.js';
12+
import { Emitter, Event } from '../../../../../../base/common/event.js';
1313
import { type ITextModel } from '../../../../../../editor/common/model.js';
1414
import { ILogService } from '../../../../../../platform/log/common/log.js';
1515
import { ILabelService } from '../../../../../../platform/label/common/label.js';
@@ -30,6 +30,7 @@ import { OffsetRange } from '../../../../../../editor/common/core/ranges/offsetR
3030
import { IChatModeInstructions, IVariableReference } from '../../chatModes.js';
3131
import { dirname } from '../../../../../../base/common/resources.js';
3232
import { IExtensionDescription } from '../../../../../../platform/extensions/common/extensions.js';
33+
import { Delayer } from '../../../../../../base/common/async.js';
3334

3435
/**
3536
* Provides prompt services.
@@ -62,7 +63,7 @@ export class PromptsService extends Disposable implements IPromptsService {
6263
/**
6364
* Lazily created event that is fired when the custom chat modes change.
6465
*/
65-
private onDidChangeCustomChatModesEvent: Event<void> | undefined;
66+
private onDidChangeCustomChatModesEmitter: Emitter<void> | undefined;
6667

6768
constructor(
6869
@ILogService public readonly logger: ILogService,
@@ -87,13 +88,15 @@ export class PromptsService extends Disposable implements IPromptsService {
8788
* Emitter for the custom chat modes change event.
8889
*/
8990
public get onDidChangeCustomChatModes(): Event<void> {
90-
if (!this.onDidChangeCustomChatModesEvent) {
91-
this.onDidChangeCustomChatModesEvent = this._register(this.fileLocator.createFilesUpdatedEvent(PromptsType.mode)).event;
92-
this._register(this.onDidChangeCustomChatModesEvent(() => {
91+
if (!this.onDidChangeCustomChatModesEmitter) {
92+
const emitter = this.onDidChangeCustomChatModesEmitter = this._register(new Emitter<void>());
93+
const chatModelTracker = this._register(new ChatModeUpdateTracker(this.fileLocator, this.modelService));
94+
this._register(chatModelTracker.onDidChangeContent(() => {
9395
this.cachedCustomChatModes = undefined; // reset cached custom chat modes
96+
emitter.fire();
9497
}));
9598
}
96-
return this.onDidChangeCustomChatModesEvent;
99+
return this.onDidChangeCustomChatModesEmitter.event;
97100
}
98101

99102
public getPromptFileType(uri: URI): PromptsType | undefined {
@@ -215,7 +218,7 @@ export class PromptsService extends Disposable implements IPromptsService {
215218
public async getCustomChatModes(token: CancellationToken): Promise<readonly ICustomChatMode[]> {
216219
if (!this.cachedCustomChatModes) {
217220
const customChatModes = this.computeCustomChatModes(token);
218-
if (!this.onDidChangeCustomChatModesEvent) {
221+
if (!this.onDidChangeCustomChatModesEmitter) {
219222
return customChatModes;
220223
}
221224
this.cachedCustomChatModes = customChatModes;
@@ -227,7 +230,7 @@ export class PromptsService extends Disposable implements IPromptsService {
227230
const modeFiles = await this.listPromptFiles(PromptsType.mode, token);
228231

229232
const customChatModes = await Promise.all(
230-
modeFiles.map(async ({ uri }): Promise<ICustomChatMode> => {
233+
modeFiles.map(async ({ uri, name: modeName }): Promise<ICustomChatMode> => {
231234
const ast = await this.parseNew(uri, token);
232235

233236
let metadata: any | undefined;
@@ -259,7 +262,7 @@ export class PromptsService extends Disposable implements IPromptsService {
259262
metadata,
260263
} satisfies IChatModeInstructions;
261264

262-
const name = getCleanPromptName(uri);
265+
const name = modeName ?? getCleanPromptName(uri);
263266
if (!ast.header) {
264267
return { uri, name, modeInstructions };
265268
}
@@ -290,12 +293,18 @@ export class PromptsService extends Disposable implements IPromptsService {
290293
return Disposable.None;
291294
}
292295
bucket.set(uri, { uri, name, description, storage: PromptsStorage.extension, type, extension } satisfies IExtensionPromptPath);
293-
if (type === PromptsType.mode) {
294-
this.cachedCustomChatModes = undefined;
295-
}
296+
297+
const updateModesIfRequired = () => {
298+
if (type === PromptsType.mode) {
299+
this.cachedCustomChatModes = undefined;
300+
this.onDidChangeCustomChatModesEmitter?.fire();
301+
}
302+
};
303+
updateModesIfRequired();
296304
return {
297305
dispose: () => {
298306
bucket.delete(uri);
307+
updateModesIfRequired();
299308
}
300309
};
301310
}
@@ -320,3 +329,54 @@ export function getPromptCommandName(path: string): string {
320329
const name = basename(path, PROMPT_FILE_EXTENSION);
321330
return name;
322331
}
332+
333+
export class ChatModeUpdateTracker extends Disposable {
334+
335+
private static readonly CHAT_MODE_UPDATE_DELAY_MS = 200;
336+
337+
private readonly listeners = new ResourceMap<IDisposable>();
338+
private readonly onDidChatModeModelChange: Emitter<void>;
339+
340+
public get onDidChangeContent(): Event<void> {
341+
return this.onDidChatModeModelChange.event;
342+
}
343+
344+
constructor(
345+
fileLocator: PromptFilesLocator,
346+
@IModelService modelService: IModelService,
347+
) {
348+
super();
349+
this.onDidChatModeModelChange = this._register(new Emitter<void>());
350+
const delayer = this._register(new Delayer<void>(ChatModeUpdateTracker.CHAT_MODE_UPDATE_DELAY_MS));
351+
const trigger = () => delayer.trigger(() => this.onDidChatModeModelChange.fire());
352+
353+
const filesUpdatedEventRegistration = this._register(fileLocator.createFilesUpdatedEvent(PromptsType.mode));
354+
this._register(filesUpdatedEventRegistration.event(() => trigger()));
355+
356+
const onAdd = (model: ITextModel) => {
357+
if (model.getLanguageId() === MODE_LANGUAGE_ID) {
358+
this.listeners.set(model.uri, model.onDidChangeContent(() => trigger()));
359+
}
360+
};
361+
const onRemove = (languageId: string, uri: URI) => {
362+
if (languageId === MODE_LANGUAGE_ID) {
363+
this.listeners.get(uri)?.dispose();
364+
this.listeners.delete(uri);
365+
trigger();
366+
}
367+
};
368+
this._register(modelService.onModelAdded(model => onAdd(model)));
369+
this._register(modelService.onModelLanguageChanged(e => {
370+
onRemove(e.oldLanguageId, e.model.uri);
371+
onAdd(e.model);
372+
}));
373+
this._register(modelService.onModelRemoved(model => onRemove(model.getLanguageId(), model.uri)));
374+
}
375+
376+
public override dispose(): void {
377+
super.dispose();
378+
this.listeners.forEach(listener => listener.dispose());
379+
this.listeners.clear();
380+
}
381+
382+
}

0 commit comments

Comments
 (0)