Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/promptSyntax/promptFileContributions.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IWorkbenchContribution } from '../../../../common/contributions.js';
7
import { PromptLinkProvider } from '../../common/promptSyntax/languageProviders/promptLinkProvider.js';
8
import { PromptBodyAutocompletion } from '../../common/promptSyntax/languageProviders/promptBodyAutocompletion.js';
9
import { PromptHeaderAutocompletion } from '../../common/promptSyntax/languageProviders/promptHeaderAutocompletion.js';
10
import { PromptHoverProvider } from '../../common/promptSyntax/languageProviders/promptHovers.js';
11
import { PromptHeaderDefinitionProvider } from '../../common/promptSyntax/languageProviders/PromptHeaderDefinitionProvider.js';
12
import { MARKERS_OWNER_ID, PromptValidator } from '../../common/promptSyntax/languageProviders/promptValidator.js';
13
import { PromptDocumentSemanticTokensProvider } from '../../common/promptSyntax/languageProviders/promptDocumentSemanticTokensProvider.js';
14
import { PromptCodeActionProvider } from '../../common/promptSyntax/languageProviders/promptCodeActions.js';
15
import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js';
16
import { Disposable, DisposableMap, DisposableStore, toDisposable } from '../../../../../base/common/lifecycle.js';
17
import { ALL_PROMPTS_LANGUAGE_SELECTOR, getPromptsTypeForLanguageId, PromptsType } from '../../common/promptSyntax/promptTypes.js';
18
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
19
import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js';
20
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
21
import { IMarkerData, IMarkerService } from '../../../../../platform/markers/common/markers.js';
22
import { ILanguageModelsService } from '../../common/languageModels.js';
23
import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js';
24
import { IChatModeService } from '../../common/chatModes.js';
25
import { IPromptsService } from '../../common/promptSyntax/service/promptsService.js';
26
import { Delayer } from '../../../../../base/common/async.js';
27
import { ITextModel } from '../../../../../editor/common/model.js';
28
import { ResourceMap } from '../../../../../base/common/map.js';
29
import { URI } from '../../../../../base/common/uri.js';
30
31
export class PromptLanguageFeaturesProvider extends Disposable implements IWorkbenchContribution {
32
static readonly ID = 'chat.promptLanguageFeatures';
33
34
constructor(
35
@ILanguageFeaturesService languageService: ILanguageFeaturesService,
36
@IInstantiationService instantiationService: IInstantiationService,
37
) {
38
super();
39
40
this._register(languageService.linkProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptLinkProvider)));
41
this._register(languageService.completionProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptBodyAutocompletion)));
42
this._register(languageService.completionProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptHeaderAutocompletion)));
43
this._register(languageService.hoverProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptHoverProvider)));
44
this._register(languageService.definitionProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptHeaderDefinitionProvider)));
45
this._register(languageService.documentSemanticTokensProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptDocumentSemanticTokensProvider)));
46
this._register(languageService.codeActionProvider.register(ALL_PROMPTS_LANGUAGE_SELECTOR, instantiationService.createInstance(PromptCodeActionProvider)));
47
48
this._register(instantiationService.createInstance(PromptValidatorContribution));
49
}
50
}
51
52
/**
53
* Tracks open code editors and validates prompt models that are visible in an editor.
54
* Only emits markers for models that are currently open in an editor.
55
*/
56
class PromptValidatorContribution extends Disposable {
57
58
private readonly validator: PromptValidator;
59
private readonly localDisposables = this._register(new DisposableStore());
60
61
constructor(
62
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
63
@IInstantiationService instantiationService: IInstantiationService,
64
@IMarkerService private readonly markerService: IMarkerService,
65
@IPromptsService private readonly promptsService: IPromptsService,
66
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService,
67
@ILanguageModelToolsService private readonly languageModelToolsService: ILanguageModelToolsService,
68
@IChatModeService private readonly chatModeService: IChatModeService,
69
) {
70
super();
71
this.validator = instantiationService.createInstance(PromptValidator);
72
73
this.updateRegistration();
74
}
75
76
updateRegistration(): void {
77
this.localDisposables.clear();
78
const trackers = new ResourceMap<ModelTracker>();
79
this.localDisposables.add(toDisposable(() => {
80
trackers.forEach(tracker => tracker.dispose());
81
trackers.clear();
82
}));
83
84
// Increment the ref count for a model, creating a tracker if needed
85
const acquire = (editor: ICodeEditor): void => {
86
const model = editor.getModel();
87
if (!model) {
88
return;
89
}
90
const promptType = getPromptsTypeForLanguageId(model.getLanguageId());
91
if (promptType) {
92
const existing = trackers.get(model.uri);
93
if (existing) {
94
existing.refCount++;
95
return;
96
}
97
trackers.set(model.uri, new ModelTracker(model, promptType, this.validator, this.promptsService, this.markerService));
98
}
99
};
100
101
// Decrement the ref count, disposing the tracker when it reaches zero
102
const release = (uri: URI): void => {
103
const tracker = trackers.get(uri);
104
if (tracker && --tracker.refCount === 0) {
105
tracker.dispose();
106
trackers.delete(uri);
107
}
108
};
109
110
const perEditorDisposables = new DisposableMap<string, DisposableStore>();
111
this.localDisposables.add(perEditorDisposables);
112
113
const onCodeEditorAdd = (editor: ICodeEditor) => {
114
acquire(editor);
115
const store = new DisposableStore();
116
// Track model changes within the editor (e.g. when a different file is opened in the same editor)
117
store.add(editor.onDidChangeModel((e) => {
118
if (e.oldModelUrl) {
119
release(e.oldModelUrl);
120
}
121
acquire(editor);
122
}));
123
store.add(editor.onDidChangeModelLanguage((e) => {
124
const model = editor.getModel();
125
if (model) {
126
release(model.uri);
127
acquire(editor);
128
}
129
}));
130
perEditorDisposables.set(editor.getId(), store);
131
};
132
133
// Track models from editors that are currently open
134
for (const editor of this.codeEditorService.listCodeEditors()) {
135
onCodeEditorAdd(editor);
136
}
137
138
// When an editor is added, start tracking its model
139
this.localDisposables.add(this.codeEditorService.onCodeEditorAdd((editor: ICodeEditor) => {
140
onCodeEditorAdd(editor);
141
}));
142
143
// When an editor is removed, clean up its per-editor listeners and release its model
144
this.localDisposables.add(this.codeEditorService.onCodeEditorRemove((editor: ICodeEditor) => {
145
perEditorDisposables.deleteAndDispose(editor.getId());
146
const model = editor.getModel();
147
if (model) {
148
release(model.uri);
149
}
150
}));
151
152
const validateAll = (): void => trackers.forEach(tracker => tracker.validate());
153
this.localDisposables.add(this.languageModelToolsService.onDidChangeTools(() => validateAll()));
154
this.localDisposables.add(this.chatModeService.onDidChangeChatModes(() => validateAll()));
155
this.localDisposables.add(this.languageModelsService.onDidChangeLanguageModels(() => validateAll()));
156
}
157
}
158
159
class ModelTracker extends Disposable {
160
161
public refCount = 1;
162
private readonly delayer: Delayer<void>;
163
164
constructor(
165
private readonly textModel: ITextModel,
166
private readonly promptType: PromptsType,
167
private readonly validator: PromptValidator,
168
private readonly promptsService: IPromptsService,
169
private readonly markerService: IMarkerService,
170
) {
171
super();
172
this.delayer = this._register(new Delayer<void>(200));
173
this._register(textModel.onDidChangeContent(() => this.validate()));
174
this.validate();
175
}
176
177
public validate(): void {
178
this.delayer.trigger(async () => {
179
const markers: IMarkerData[] = [];
180
const ast = this.promptsService.getParsedPromptFile(this.textModel);
181
await this.validator.validate(ast, this.promptType, m => markers.push(m));
182
if (!this._store.isDisposed) {
183
this.markerService.changeOne(MARKERS_OWNER_ID, this.textModel.uri, markers);
184
}
185
});
186
}
187
188
public override dispose() {
189
this.markerService.remove(MARKERS_OWNER_ID, [this.textModel.uri]);
190
super.dispose();
191
}
192
}
193
194