Skip to content

Commit c894de7

Browse files
authored
support the "object" variant for the prompt files configuration setting value (microsoft#239242)
[config]: support the "object" variant for the setting value
1 parent 4e144a1 commit c894de7

File tree

3 files changed

+105
-32
lines changed

3 files changed

+105
-32
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ configurationRegistry.registerConfiguration({
154154
default: true
155155
},
156156
[PromptFilesConfig.CONFIG_KEY]: {
157-
type: ['string', 'array', 'boolean', 'null'],
157+
type: ['string', 'array', 'object', 'boolean', 'null'],
158158
title: nls.localize('chat.promptFiles.setting.title', "Prompt Files"),
159159
markdownDescription: nls.localize(
160160
'chat.promptFiles.setting.markdownDescription',

src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { URI } from '../../../../../base/common/uri.js';
7+
import { PromptFilesConfig } from '../../common/promptSyntax/config.js';
78
import { dirname, extUri } from '../../../../../base/common/resources.js';
89
import { IFileService } from '../../../../../platform/files/common/files.js';
9-
import { PromptFilesConfig } from '../../common/promptSyntax/config.js';
1010
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
1111
import { IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js';
1212
import { PROMPT_SNIPPET_FILE_EXTENSION } from '../../common/promptSyntax/contentProviders/promptContentsProviderBase.js';

src/vs/workbench/contrib/chat/common/promptSyntax/config.ts

Lines changed: 103 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,39 @@ import { IConfigurationService } from '../../../../../platform/configuration/com
3535
* Enable the feature, specifying multiple prompt files source folder location:
3636
* ```json
3737
* {
38+
* "chat.experimental.promptSnippets": {
39+
* ".github/prompts" : true,
40+
* ".copilot/prompts" : false,
41+
* "/Users/legomushroom/repos/prompts" : true,
42+
* },
43+
* }
44+
* ```
45+
*
46+
* Enable the feature, specifying multiple prompt files source folder location:
47+
* ```json
48+
* {
3849
* "chat.experimental.promptSnippets": [
39-
* '.github/prompts',
40-
* '.copilot/prompts',
41-
* '/Users/legomushroom/repos/prompts',
50+
* ".github/prompts",
51+
* ".copilot/prompts",
52+
* "/Users/legomushroom/repos/prompts",
4253
* ],
4354
* }
4455
* ```
4556
*
57+
* The "array" case is similar to the "object" one, but there is one difference.
58+
* At the time of writing, configuration settings with the `array` value cannot
59+
* be merged into a single entry when the setting is specified in both the user
60+
* and the workspace settings. On the other hand, the "object" case is provides
61+
* flexibility - the settings are combined into a single object.
62+
*
63+
* Enable the feature, using defaults for prompt files source folder locations
64+
* (see {@link DEFAULT_LOCATION}):
65+
* ```json
66+
* {
67+
* "chat.experimental.promptSnippets": {},
68+
* }
69+
* ```
70+
*
4671
* See the next section for details on how we treat the config value.
4772
*
4873
* ### Possible Values
@@ -54,13 +79,24 @@ import { IConfigurationService } from '../../../../../platform/configuration/com
5479
* - `false`: feature is disabled
5580
* - `string`:
5681
* - values that can be mapped to `boolean`(`"true"`, `"FALSE", "TrUe"`, etc.)
57-
* are treated as `boolean` above
82+
* are treated the same as the `boolean` case above
5883
* - any other `non-empty` string value is treated as a single prompt files source folder path
84+
* - `empty` string value is treated the same as the `undefined`/`null` case above
85+
* - `object`:
86+
* - expects the { "string": `boolean` } pairs, where the `string` is a path and the `boolean`
87+
* is a flag that defines if the source folder location is enable or disabled
88+
* - value of a record in the object can also be a `string`:
89+
* - if the string can be clearly mapped to a `boolean` (e.g., `"true"`, `"FALSE", "TrUe"`, etc.),
90+
* it is treated as `boolean` value
91+
* - any other string value is treated as `false` and is effectively ignored
92+
* - if the record key is an `empty` string, it is ignored
93+
* - if the resulting object is empty, the feature is considered `enabled`, prompt files source
94+
* folder locations fallback to {@link DEFAULT_LOCATION}
5995
* - `array`:
6096
* - `string` items in the array are treated as prompt files source folder paths
6197
* - all `non-string` items in the array are `ignored`
62-
* - if the resulting array is empty, the feature is considered `enabled`, prompt files source
63-
* folder locations fallback to defaults (see {@linkcode DEFAULT_LOCATION})
98+
* - if the resulting array is empty, the feature is considered `enabled`, prompt files
99+
* source folder locations fallback to {@link DEFAULT_LOCATION}
64100
*
65101
* ### File Paths Resolution
66102
*
@@ -87,47 +123,59 @@ export namespace PromptFilesConfig {
87123
/**
88124
* Default prompt instructions source folder paths.
89125
*/
90-
const DEFAULT_LOCATION = ['.github/prompts'];
126+
const DEFAULT_LOCATION = Object.freeze(['.github/prompts']);
91127

92128
/**
93129
* Get value of the `prompt files` configuration setting.
94130
*/
95131
export const getValue = (
96132
configService: IConfigurationService,
97133
): string | readonly string[] | boolean | undefined => {
98-
const value = configService.getValue(CONFIG_KEY);
134+
const configValue = configService.getValue(CONFIG_KEY);
99135

100-
if (value === undefined || value === null) {
136+
if (configValue === undefined || configValue === null) {
101137
return undefined;
102138
}
103139

104-
if (typeof value === 'string') {
105-
const cleanValue = value.trim().toLowerCase();
106-
if (cleanValue === 'true') {
107-
return true;
108-
}
109-
110-
if (cleanValue === 'false') {
111-
return false;
112-
}
140+
if (typeof configValue === 'string') {
141+
const cleanValue = configValue.trim().toLowerCase();
113142

114143
if (!cleanValue) {
115144
return undefined;
116145
}
117146

118-
return value;
147+
if (asBoolean(cleanValue) !== undefined) {
148+
return asBoolean(cleanValue);
149+
}
150+
151+
return cleanValue;
119152
}
120153

121-
if (typeof value === 'boolean') {
122-
return value;
154+
if (typeof configValue === 'boolean') {
155+
return configValue;
123156
}
124157

125-
if (Array.isArray(value)) {
126-
return value.filter((item) => {
158+
if (Array.isArray(configValue)) {
159+
return configValue.filter((item) => {
127160
return typeof item === 'string';
128161
});
129162
}
130163

164+
// note! this would be also true for `null` and `array`,
165+
// but those cases are already handled above
166+
if (typeof configValue === 'object') {
167+
const paths: string[] = [];
168+
169+
for (const [path, value] of Object.entries(configValue)) {
170+
const cleanPath = path.trim();
171+
if (asBoolean(value) && cleanPath) {
172+
paths.push(cleanPath);
173+
}
174+
}
175+
176+
return Object.freeze(paths);
177+
}
178+
131179
return undefined;
132180
};
133181

@@ -156,17 +204,42 @@ export namespace PromptFilesConfig {
156204
}
157205

158206
if (typeof value === 'string') {
159-
return [value];
207+
return Object.freeze([value]);
160208
}
161209

162-
if (Array.isArray(value)) {
163-
if (value.length !== 0) {
164-
return value;
165-
}
166-
167-
return DEFAULT_LOCATION;
210+
if (Array.isArray(value) && value.length !== 0) {
211+
return value;
168212
}
169213

170214
return DEFAULT_LOCATION;
171215
};
172216
}
217+
218+
/**
219+
* Helper to parse an input value of `any` type into a boolean.
220+
*
221+
* @param value - input value to parse
222+
* @returns `true` if the value is a boolean `true` or a string that can
223+
* be clearly mapped to a boolean (e.g., `"true"`, `"TRUE"`, `"FaLSe"`, etc.),
224+
* `undefined` for rest of the values
225+
*/
226+
function asBoolean(value: any): boolean | undefined {
227+
if (typeof value === 'boolean') {
228+
return value;
229+
}
230+
231+
if (typeof value === 'string') {
232+
const cleanValue = value.trim().toLowerCase();
233+
if (cleanValue === 'true') {
234+
return true;
235+
}
236+
237+
if (cleanValue === 'false') {
238+
return false;
239+
}
240+
241+
return undefined;
242+
}
243+
244+
return undefined;
245+
}

0 commit comments

Comments
 (0)