@@ -10,7 +10,7 @@ import {
1010 TextEditor ,
1111 StatusBarItem ,
1212 QuickPickItem ,
13- ThemeColor ,
13+ ThemeIcon ,
1414 workspace ,
1515 Uri ,
1616} from 'vscode' ;
@@ -49,10 +49,12 @@ const getSchemaRequestType: RequestType<FileUri, JSONSchema[], {}> = new Request
4949export let statusBarItem : StatusBarItem ;
5050
5151let client : CommonLanguageClient ;
52- let versionSelection : SchemaItem = undefined ;
5352
54- const selectVersionLabel = 'Select Different Version' ;
5553const noSchemaLabel = 'No JSON Schema' ;
54+ const selectSchemaVersionButton = {
55+ iconPath : new ThemeIcon ( 'versions' ) ,
56+ tooltip : 'Select schema version' ,
57+ } ;
5658
5759export function createJSONSchemaStatusBarItem ( context : ExtensionContext , languageclient : CommonLanguageClient ) : void {
5860 if ( statusBarItem ) {
@@ -69,45 +71,49 @@ export function createJSONSchemaStatusBarItem(context: ExtensionContext, languag
6971 context . subscriptions . push ( statusBarItem ) ;
7072
7173 context . subscriptions . push ( window . onDidChangeActiveTextEditor ( updateStatusBar ) ) ;
74+ context . subscriptions . push (
75+ workspace . onDidChangeTextDocument ( ( event ) => {
76+ const editor = window . activeTextEditor ;
77+ if (
78+ editor ?. document . languageId === 'yaml' &&
79+ editor . document . uri . toString ( ) === event . document . uri . toString ( ) &&
80+ event . contentChanges ?. some ( ( change ) => / # \s * y a m l - l a n g u a g e - s e r v e r : \s * \$ s c h e m a = / . test ( change . text ) )
81+ ) {
82+ updateStatusBar ( editor ) ;
83+ }
84+ } )
85+ ) ;
7286
7387 updateStatusBar ( window . activeTextEditor ) ;
7488}
7589
7690async function updateStatusBar ( editor : TextEditor ) : Promise < void > {
7791 if ( editor && editor . document . languageId === 'yaml' ) {
78- versionSelection = undefined ;
7992 const fileUri = editor . document . uri . toString ( ) ;
8093 // get schema info there
8194 const schema = await client . sendRequest ( getSchemaRequestType , fileUri ) ;
8295 if ( ! schema || schema . length === 0 ) {
8396 statusBarItem . text = noSchemaLabel ;
84- statusBarItem . tooltip = 'Select JSON Schema' ;
85- statusBarItem . backgroundColor = undefined ;
97+ statusBarItem . tooltip = 'Select JSON Schemas' ;
8698 } else if ( schema . length === 1 ) {
99+ statusBarItem . tooltip = 'Select JSON Schemas' ;
87100 statusBarItem . text = schema [ 0 ] . name ?? schema [ 0 ] . uri ;
88101 let version ;
89102 if ( schema [ 0 ] . versions ) {
90103 version = findUsedVersion ( schema [ 0 ] . versions , schema [ 0 ] . uri ) ;
91104 } else {
92105 const schemas = await client . sendRequest ( getJSONSchemasRequestType , fileUri ) ;
93- let versionSchema : JSONSchema ;
94106 const schemaStoreItem = findSchemaStoreItem ( schemas , schema [ 0 ] . uri ) ;
95107 if ( schemaStoreItem ) {
96- [ version , versionSchema ] = schemaStoreItem ;
97- ( versionSchema as MatchingJSONSchema ) . usedForCurrentFile = true ;
98- versionSchema . uri = schema [ 0 ] . uri ;
99- versionSelection = createSelectVersionItem ( version , versionSchema as MatchingJSONSchema ) ;
108+ version = schemaStoreItem [ 0 ] ;
100109 }
101110 }
102111 if ( version && ! statusBarItem . text . includes ( version ) ) {
103112 statusBarItem . text += `(${ version } )` ;
104113 }
105- statusBarItem . tooltip = 'Select JSON Schema' ;
106- statusBarItem . backgroundColor = undefined ;
107114 } else {
108- statusBarItem . text = 'Multiple JSON Schemas...' ;
109- statusBarItem . tooltip = 'Multiple JSON Schema used to validate this file, click to select one' ;
110- statusBarItem . backgroundColor = new ThemeColor ( 'statusBarItem.warningBackground' ) ;
115+ statusBarItem . text = 'Multiple JSON Schemas' ;
116+ statusBarItem . tooltip = `Validated using ${ schema . length } JSON schemas:\n${ schema . map ( ( s ) => s . name ?? s . uri ) . join ( '\n' ) } ` ;
111117 }
112118
113119 statusBarItem . show ( ) ;
@@ -123,22 +129,16 @@ async function showSchemaSelection(): Promise<void> {
123129 let pickItems : SchemaItem [ ] = [ ] ;
124130
125131 for ( const val of schemas ) {
126- if ( val . usedForCurrentFile && val . versions ) {
127- const item = createSelectVersionItem ( findUsedVersion ( val . versions , val . uri ) , val ) ;
128- pickItems . unshift ( item ) ;
129- }
130132 const item = {
131133 label : val . name ?? val . uri ,
132134 description : val . description ,
133135 detail : val . usedForCurrentFile ? 'Used for current file$(check)' : '' ,
134136 alwaysShow : val . usedForCurrentFile ,
137+ buttons : val . versions ? [ selectSchemaVersionButton ] : undefined ,
135138 schema : val ,
136139 } ;
137140 pickItems . push ( item ) ;
138141 }
139- if ( versionSelection ) {
140- pickItems . unshift ( versionSelection ) ;
141- }
142142
143143 pickItems = pickItems . sort ( ( a , b ) => {
144144 if ( a . schema ?. usedForCurrentFile && a . schema ?. versions ) {
@@ -156,26 +156,39 @@ async function showSchemaSelection(): Promise<void> {
156156 return a . label . localeCompare ( b . label ) ;
157157 } ) ;
158158
159- pickItems . unshift ( {
159+ const noSchemaItem : SchemaItem = {
160160 label : noSchemaLabel ,
161161 alwaysShow : true ,
162162 disableSchemaDetection : true ,
163- } ) ;
163+ } ;
164+ pickItems . unshift ( noSchemaItem ) ;
164165
165166 schemasPick . items = pickItems ;
167+ schemasPick . canSelectMany = true ;
168+ const selectedSchemaItems = pickItems . filter ( ( item ) => item . schema ?. usedForCurrentFile ) ;
169+ schemasPick . selectedItems =
170+ selectedSchemaItems . length > 0 && ! isSchemaDetectionDisabled ( fileUri ) ? selectedSchemaItems : [ noSchemaItem ] ;
166171 schemasPick . placeholder = 'Search JSON schema' ;
167- schemasPick . title = 'Select JSON schema ' ;
172+ schemasPick . title = 'Select JSON schemas ' ;
168173 schemasPick . onDidHide ( ( ) => schemasPick . dispose ( ) ) ;
174+ schemasPick . onDidTriggerItemButton ( ( event ) => {
175+ if ( event . button === selectSchemaVersionButton && event . item . schema ?. versions ) {
176+ const selectedSchemaUris = schemasPick . selectedItems . flatMap ( ( item ) => ( item . schema ? [ item . schema . uri ] : [ ] ) ) ;
177+ schemasPick . hide ( ) ;
178+ handleSchemaVersionSelection ( event . item . schema , fileUri , selectedSchemaUris ) ;
179+ }
180+ } ) ;
169181
170- schemasPick . onDidChangeSelection ( ( selection ) => {
182+ schemasPick . onDidAccept ( async ( ) => {
171183 try {
172- if ( selection . length > 0 ) {
173- if ( selection [ 0 ] . label === selectVersionLabel ) {
174- handleSchemaVersionSelection ( selection [ 0 ] . schema , fileUri ) ;
175- } else if ( selection [ 0 ] . disableSchemaDetection ) {
176- writeDisableSchemaDetectionMapping ( fileUri ) ;
177- } else if ( selection [ 0 ] . schema ) {
178- writeSchemaUriMapping ( selection [ 0 ] . schema . uri , fileUri ) ;
184+ if ( schemasPick . selectedItems . length === 0 || schemasPick . selectedItems . some ( ( item ) => item . disableSchemaDetection ) ) {
185+ await writeDisableSchemaDetectionMapping ( fileUri ) ;
186+ } else {
187+ const schemaUrls = schemasPick . selectedItems . flatMap ( ( item ) => ( item . schema ? [ item . schema . uri ] : [ ] ) ) ;
188+ if ( schemaUrls . length === 0 ) {
189+ await writeDisableSchemaDetectionMapping ( fileUri ) ;
190+ } else {
191+ await writeSchemaUriMappings ( schemaUrls , fileUri ) ;
179192 }
180193 }
181194 } catch ( err ) {
@@ -200,6 +213,15 @@ function getFilePatternCandidates(fileUri: string): string[] {
200213 return Array . from ( candidates ) . filter ( Boolean ) ;
201214}
202215
216+ function isSchemaDetectionDisabled ( fileUri : string ) : boolean {
217+ const disableSchemaDetection = workspace . getConfiguration ( 'yaml' ) . get ( 'disableSchemaDetection' ) ;
218+ const filePatterns = getFilePatternCandidates ( fileUri ) ;
219+ if ( Array . isArray ( disableSchemaDetection ) ) {
220+ return disableSchemaDetection . some ( ( value ) => typeof value === 'string' && filePatterns . includes ( value ) ) ;
221+ }
222+ return typeof disableSchemaDetection === 'string' && filePatterns . includes ( disableSchemaDetection ) ;
223+ }
224+
203225function deleteExistingFilePattern ( settings : Record < string , unknown > , filePatterns : string [ ] ) : unknown {
204226 for ( const key in settings ) {
205227 if ( Object . prototype . hasOwnProperty . call ( settings , key ) ) {
@@ -253,14 +275,6 @@ function addFilePatternToSetting(setting: unknown, fileUri: string): string | st
253275 return [ fileUri ] ;
254276}
255277
256- function createSelectVersionItem ( version : string , schema : MatchingJSONSchema ) : SchemaItem {
257- return {
258- label : selectVersionLabel ,
259- detail : `Current: ${ version } ` ,
260- alwaysShow : true ,
261- schema : schema ,
262- } ;
263- }
264278function findSchemaStoreItem ( schemas : JSONSchema [ ] , url : string ) : [ string , JSONSchema ] | undefined {
265279 for ( const schema of schemas ) {
266280 if ( schema . versions ) {
@@ -273,34 +287,41 @@ function findSchemaStoreItem(schemas: JSONSchema[], url: string): [string, JSONS
273287 }
274288}
275289
276- function writeSchemaUriMapping ( schemaUrl : string , fileUri : string ) : void {
290+ async function writeSchemaUriMappings ( schemaUrls : string [ ] , fileUri : string ) : Promise < void > {
277291 const yamlConfiguration = workspace . getConfiguration ( 'yaml' ) ;
278292 const settings : Record < string , unknown > = yamlConfiguration . get ( 'schemas' ) ;
279293 const disableSchemaDetection = yamlConfiguration . get ( 'disableSchemaDetection' ) ;
280294 const filePatterns = getFilePatternCandidates ( fileUri ) ;
281- yamlConfiguration . update ( 'disableSchemaDetection' , removeFilePatternFromSetting ( disableSchemaDetection , filePatterns ) ) ;
295+ await yamlConfiguration . update ( 'disableSchemaDetection' , removeFilePatternFromSetting ( disableSchemaDetection , filePatterns ) ) ;
282296 const newSettings = Object . assign ( { } , settings ) ;
283297 deleteExistingFilePattern ( newSettings , filePatterns ) ;
284- const schemaSettings = newSettings [ schemaUrl ] ;
285- if ( schemaSettings ) {
286- if ( Array . isArray ( schemaSettings ) ) {
287- ( schemaSettings as Array < string > ) . push ( fileUri ) ;
288- } else if ( typeof schemaSettings === 'string' ) {
289- newSettings [ schemaUrl ] = [ schemaSettings , fileUri ] ;
298+
299+ for ( const schemaUrl of schemaUrls ) {
300+ const schemaSettings = newSettings [ schemaUrl ] ;
301+ if ( schemaSettings ) {
302+ if ( Array . isArray ( schemaSettings ) ) {
303+ const schemaFilePatterns = schemaSettings . filter ( ( value ) : value is string => typeof value === 'string' ) ;
304+ if ( ! schemaFilePatterns . includes ( fileUri ) ) {
305+ schemaFilePatterns . push ( fileUri ) ;
306+ }
307+ newSettings [ schemaUrl ] = schemaFilePatterns ;
308+ } else if ( typeof schemaSettings === 'string' ) {
309+ newSettings [ schemaUrl ] = schemaSettings === fileUri ? schemaSettings : [ schemaSettings , fileUri ] ;
310+ }
311+ } else {
312+ newSettings [ schemaUrl ] = fileUri ;
290313 }
291- } else {
292- newSettings [ schemaUrl ] = fileUri ;
293314 }
294- yamlConfiguration . update ( 'schemas' , newSettings ) ;
315+ await yamlConfiguration . update ( 'schemas' , newSettings ) ;
295316}
296317
297- function writeDisableSchemaDetectionMapping ( fileUri : string ) : void {
318+ async function writeDisableSchemaDetectionMapping ( fileUri : string ) : Promise < void > {
298319 const yamlConfiguration = workspace . getConfiguration ( 'yaml' ) ;
299320 const disableSchemaDetection = yamlConfiguration . get ( 'disableSchemaDetection' ) ;
300- yamlConfiguration . update ( 'disableSchemaDetection' , addFilePatternToSetting ( disableSchemaDetection , fileUri ) ) ;
321+ await yamlConfiguration . update ( 'disableSchemaDetection' , addFilePatternToSetting ( disableSchemaDetection , fileUri ) ) ;
301322}
302323
303- function handleSchemaVersionSelection ( schema : MatchingJSONSchema , fileUri : string ) : void {
324+ function handleSchemaVersionSelection ( schema : MatchingJSONSchema , fileUri : string , selectedSchemaUris : string [ ] ) : void {
304325 const versionPick = window . createQuickPick < SchemaVersionItem > ( ) ;
305326 const versionItems : SchemaVersionItem [ ] = [ ] ;
306327 const usedVersion = findUsedVersion ( schema . versions , schema . uri ) ;
@@ -313,13 +334,18 @@ function handleSchemaVersionSelection(schema: MatchingJSONSchema, fileUri: strin
313334 }
314335
315336 versionPick . items = versionItems ;
316- versionPick . title = `Select JSON Schema version for ${ schema . name } ` ;
337+ versionPick . title = `Select JSON Schema version for ${ schema . name ?? schema . uri } ` ;
317338 versionPick . placeholder = 'Version' ;
318339 versionPick . onDidHide ( ( ) => versionPick . dispose ( ) ) ;
319340
320- versionPick . onDidChangeSelection ( ( items ) => {
341+ versionPick . onDidChangeSelection ( async ( items ) => {
321342 if ( items && items . length === 1 ) {
322- writeSchemaUriMapping ( items [ 0 ] . url , fileUri ) ;
343+ const schemaVersionUris = new Set ( Object . values ( schema . versions ?? { } ) ) ;
344+ const remainingSchemaUris = selectedSchemaUris . filter (
345+ ( schemaUri ) => schemaUri !== schema . uri && ! schemaVersionUris . has ( schemaUri )
346+ ) ;
347+ const updatedSchemaUris = Array . from ( new Set ( [ ...remainingSchemaUris , items [ 0 ] . url ] ) ) ;
348+ await writeSchemaUriMappings ( updatedSchemaUris , fileUri ) ;
323349 }
324350 versionPick . hide ( ) ;
325351 } ) ;
0 commit comments