@@ -35,14 +35,39 @@ import { IConfigurationService } from '../../../../../platform/configuration/com
35
35
* Enable the feature, specifying multiple prompt files source folder location:
36
36
* ```json
37
37
* {
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
+ * {
38
49
* "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" ,
42
53
* ],
43
54
* }
44
55
* ```
45
56
*
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
+ *
46
71
* See the next section for details on how we treat the config value.
47
72
*
48
73
* ### Possible Values
@@ -54,13 +79,24 @@ import { IConfigurationService } from '../../../../../platform/configuration/com
54
79
* - `false`: feature is disabled
55
80
* - `string`:
56
81
* - 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
58
83
* - 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}
59
95
* - `array`:
60
96
* - `string` items in the array are treated as prompt files source folder paths
61
97
* - 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}
64
100
*
65
101
* ### File Paths Resolution
66
102
*
@@ -87,47 +123,59 @@ export namespace PromptFilesConfig {
87
123
/**
88
124
* Default prompt instructions source folder paths.
89
125
*/
90
- const DEFAULT_LOCATION = [ '.github/prompts' ] ;
126
+ const DEFAULT_LOCATION = Object . freeze ( [ '.github/prompts' ] ) ;
91
127
92
128
/**
93
129
* Get value of the `prompt files` configuration setting.
94
130
*/
95
131
export const getValue = (
96
132
configService : IConfigurationService ,
97
133
) : string | readonly string [ ] | boolean | undefined => {
98
- const value = configService . getValue ( CONFIG_KEY ) ;
134
+ const configValue = configService . getValue ( CONFIG_KEY ) ;
99
135
100
- if ( value === undefined || value === null ) {
136
+ if ( configValue === undefined || configValue === null ) {
101
137
return undefined ;
102
138
}
103
139
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 ( ) ;
113
142
114
143
if ( ! cleanValue ) {
115
144
return undefined ;
116
145
}
117
146
118
- return value ;
147
+ if ( asBoolean ( cleanValue ) !== undefined ) {
148
+ return asBoolean ( cleanValue ) ;
149
+ }
150
+
151
+ return cleanValue ;
119
152
}
120
153
121
- if ( typeof value === 'boolean' ) {
122
- return value ;
154
+ if ( typeof configValue === 'boolean' ) {
155
+ return configValue ;
123
156
}
124
157
125
- if ( Array . isArray ( value ) ) {
126
- return value . filter ( ( item ) => {
158
+ if ( Array . isArray ( configValue ) ) {
159
+ return configValue . filter ( ( item ) => {
127
160
return typeof item === 'string' ;
128
161
} ) ;
129
162
}
130
163
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
+
131
179
return undefined ;
132
180
} ;
133
181
@@ -156,17 +204,42 @@ export namespace PromptFilesConfig {
156
204
}
157
205
158
206
if ( typeof value === 'string' ) {
159
- return [ value ] ;
207
+ return Object . freeze ( [ value ] ) ;
160
208
}
161
209
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 ;
168
212
}
169
213
170
214
return DEFAULT_LOCATION ;
171
215
} ;
172
216
}
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