Skip to content

Commit b846368

Browse files
committed
II of the PromptsLinkDiagnosticsProvider contribution
1 parent b46b9de commit b846368

File tree

3 files changed

+221
-144
lines changed

3 files changed

+221
-144
lines changed

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { ILanguageModelStatsService, LanguageModelStatsService } from '../common
4242
import { ILanguageModelToolsService } from '../common/languageModelToolsService.js';
4343
import '../common/promptSyntax/languageFeatures/promptLinkProvider.js';
4444
import '../common/promptSyntax/languageFeatures/promptPathAutocompletion.js';
45+
import '../common/promptSyntax/languageFeatures/promptLinkDiagnosticsProvider.js';
4546
import { PromptsService } from '../common/promptSyntax/service/promptsService.js';
4647
import { IPromptsService } from '../common/promptSyntax/service/types.js';
4748
import './promptSyntax/contributions/usePromptCommand.js';

src/vs/workbench/contrib/chat/browser/promptSyntax/languageFeatures/markersProvider.ts

Lines changed: 0 additions & 144 deletions
This file was deleted.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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 { IPromptsService } from '../service/types.js';
7+
import { IPromptFileReference } from '../parsers/types.js';
8+
import { assert } from '../../../../../../base/common/assert.js';
9+
import { NotPromptFile } from '../../promptFileReferenceErrors.js';
10+
import { ITextModel } from '../../../../../../editor/common/model.js';
11+
import { assertDefined } from '../../../../../../base/common/types.js';
12+
import { Disposable } from '../../../../../../base/common/lifecycle.js';
13+
import { IEditor } from '../../../../../../editor/common/editorCommon.js';
14+
import { ObjectCache } from '../../../../../../base/common/objectCache.js';
15+
import { TextModelPromptParser } from '../parsers/textModelPromptParser.js';
16+
import { Registry } from '../../../../../../platform/registry/common/platform.js';
17+
import { PromptsConfig } from '../../../../../../platform/prompts/common/config.js';
18+
import { isPromptFile } from '../../../../../../platform/prompts/common/constants.js';
19+
import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecycle.js';
20+
import { IEditorService } from '../../../../../services/editor/common/editorService.js';
21+
import { ObservableDisposable } from '../../../../../../base/common/observableDisposable.js';
22+
import { IWorkbenchContributionsRegistry, Extensions } from '../../../../../common/contributions.js';
23+
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
24+
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
25+
import { IMarkerData, IMarkerService, MarkerSeverity } from '../../../../../../platform/markers/common/markers.js';
26+
27+
/**
28+
* TODO: @legomushroom - list
29+
* - improve error messages
30+
*/
31+
32+
/**
33+
* TODO: @legomushroom
34+
*/
35+
const MARKERS_OWNER_ID = 'reusable-prompts-syntax';
36+
37+
/**
38+
* TODO: @legomushroom
39+
*/
40+
class PromptLinkDiagnosticsProvider extends ObservableDisposable {
41+
/**
42+
* TODO: @legomushroom
43+
*/
44+
private readonly parser: TextModelPromptParser;
45+
46+
constructor(
47+
private readonly editor: ITextModel,
48+
@IMarkerService private readonly markerService: IMarkerService,
49+
@IPromptsService private readonly promptsService: IPromptsService,
50+
) {
51+
super();
52+
53+
this.parser = this.promptsService
54+
.getSyntaxParserFor(this.editor)
55+
.onUpdate(this.updateMarkers.bind(this))
56+
.onDispose(this.dispose.bind(this))
57+
.start();
58+
59+
// initialize markers
60+
this.updateMarkers();
61+
}
62+
63+
/**
64+
* TODO: @legomushroom
65+
*/
66+
private async updateMarkers() {
67+
// ensure that parsing process is settled
68+
await this.parser.allSettled();
69+
70+
// clean up all previously added markers
71+
this.markerService.remove(MARKERS_OWNER_ID, [this.editor.uri]);
72+
73+
const markers: IMarkerData[] = [];
74+
for (const link of this.parser.references) {
75+
const { topError, linkRange } = link;
76+
77+
if (!topError || !linkRange) {
78+
continue;
79+
}
80+
81+
// the `NotPromptFile` error is allowed because we allow users
82+
// to include non-prompt file links in the prompt files
83+
// note! this check also handles the `FolderReference` error
84+
if (topError instanceof NotPromptFile) {
85+
continue;
86+
}
87+
88+
markers.push(toMarker(link));
89+
}
90+
91+
this.markerService.changeOne(
92+
MARKERS_OWNER_ID,
93+
this.editor.uri,
94+
markers,
95+
);
96+
}
97+
}
98+
99+
/**
100+
* TODO: @legomushroom
101+
*/
102+
// TODO: @legomushroom - add @throws info
103+
const toMarker = (
104+
link: IPromptFileReference,
105+
): IMarkerData => {
106+
const { topError, linkRange } = link;
107+
108+
// a sanity check because this function must be
109+
// used only if these attributes are present
110+
assertDefined(
111+
topError,
112+
'Error condition must to be defined.',
113+
);
114+
assertDefined(
115+
linkRange,
116+
'Link range must to be defined.',
117+
);
118+
assert(
119+
!(topError instanceof NotPromptFile),
120+
'Error must not be of "not prompt file" type.',
121+
);
122+
123+
// use `error` severity if the error relates to the link itself, and use
124+
// the `warning` severity if the error is related to one of its children
125+
const severity = (topError.isRootError)
126+
? MarkerSeverity.Error
127+
: MarkerSeverity.Warning;
128+
129+
return {
130+
message: topError.message,
131+
severity,
132+
...linkRange,
133+
};
134+
};
135+
136+
/**
137+
* TODO: @legomushroom
138+
*/
139+
export class PromptsLinkDiagnosticsProvider extends Disposable {
140+
/**
141+
* TODO: @legomushroom
142+
*/
143+
private readonly providers: ObjectCache<PromptLinkDiagnosticsProvider, ITextModel>;
144+
145+
constructor(
146+
@IEditorService editorService: IEditorService,
147+
@IInstantiationService initService: IInstantiationService,
148+
@IConfigurationService configService: IConfigurationService,
149+
) {
150+
super();
151+
152+
// cache of prompt marker providers
153+
this.providers = this._register(
154+
new ObjectCache((editor: ITextModel) => {
155+
const parser: PromptLinkDiagnosticsProvider = initService.createInstance(
156+
PromptLinkDiagnosticsProvider,
157+
editor,
158+
);
159+
160+
// this is a sanity check and the contract of the object cache,
161+
// we must return a non-disposed object from this factory function
162+
parser.assertNotDisposed(
163+
'Created prompt parser must not be disposed.',
164+
);
165+
166+
return parser;
167+
}),
168+
);
169+
170+
// if the feature is disabled, do not create any providers
171+
if (!PromptsConfig.enabled(configService)) {
172+
return;
173+
}
174+
175+
// subscribe to changes of the active editor
176+
this._register(editorService.onDidActiveEditorChange(() => {
177+
const { activeTextEditorControl } = editorService;
178+
if (!activeTextEditorControl) {
179+
return;
180+
}
181+
182+
this.handleNewEditor(activeTextEditorControl);
183+
}));
184+
185+
// handle existing visible text editors
186+
editorService
187+
.visibleTextEditorControls
188+
.forEach(this.handleNewEditor.bind(this));
189+
}
190+
191+
/**
192+
* TODO: @legomushroom
193+
*/
194+
private handleNewEditor(editor: IEditor): this {
195+
const model = editor.getModel();
196+
if (!model) {
197+
return this;
198+
}
199+
200+
// we support only `text editors` for now so filter out `diff` ones
201+
if ('modified' in model || 'model' in model) {
202+
return this;
203+
}
204+
205+
// enable this only for prompt file editors
206+
if (!isPromptFile(model.uri)) {
207+
return this;
208+
}
209+
210+
// note! calling `get` also creates a provider if it does not exist;
211+
// and the provider is auto-removed when the model is disposed
212+
this.providers.get(model);
213+
214+
return this;
215+
}
216+
}
217+
218+
// register the provider as a workbench contribution
219+
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench)
220+
.registerWorkbenchContribution(PromptsLinkDiagnosticsProvider, LifecyclePhase.Eventually);

0 commit comments

Comments
 (0)