diff --git a/language-server/src/server.js b/language-server/src/server.js index 4950c4e..e81c279 100644 --- a/language-server/src/server.js +++ b/language-server/src/server.js @@ -5,6 +5,7 @@ import { DiagnosticTag, DidChangeWatchedFilesNotification, ProposedFeatures, + DidChangeConfigurationNotification, SemanticTokensBuilder, TextDocuments, TextDocumentSyncKind @@ -38,9 +39,13 @@ connection.console.log("Starting JSON Schema service ..."); let hasWorkspaceFolderCapability = false; let hasWorkspaceWatchCapability = false; +let hasConfigurationCapability = false; connection.onInitialize(({ capabilities, workspaceFolders }) => { connection.console.log("Initializing JSON Schema service ..."); + hasConfigurationCapability = !!( + capabilities.workspace && !!capabilities.workspace.configuration + ); if (workspaceFolders) { addWorkspaceFolders(workspaceFolders); @@ -73,6 +78,10 @@ connection.onInitialize(({ capabilities, workspaceFolders }) => { }); connection.onInitialized(async () => { + if (hasConfigurationCapability) { + connection.client.register(DidChangeConfigurationNotification.type); + } + if (hasWorkspaceWatchCapability) { connection.client.register(DidChangeWatchedFilesNotification.type, { watchers: [ @@ -136,6 +145,37 @@ connection.listen(); const documents = new TextDocuments(TextDocument); +// CONFIGURATION + +const documentSettings = new Map(); +let globalSettings = {}; + +async function getDocumentSettings(resource) { + if (!hasConfigurationCapability) { + return globalSettings; + } + + if (!documentSettings.has(resource)) { + const result = await connection.workspace.getConfiguration({ + scopeUri: resource, + section: "jsonSchemaLanguageServer" + }); + documentSettings.set(resource, result ?? globalSettings); + } + + return documentSettings.get(resource); +} + +connection.onDidChangeConfiguration((change) => { + if (hasConfigurationCapability) { + documentSettings.clear(); + } else { + globalSettings = change.settings.jsonSchemaLanguageServer ?? globalSettings; + } + + validateWorkspace(); +}); + // INLINE ERRORS documents.onDidChangeContent(async ({ document }) => { @@ -150,19 +190,22 @@ documents.onDidChangeContent(async ({ document }) => { const validateSchema = async (document) => { const diagnostics = []; + const settings = await getDocumentSettings(document.uri); const instance = JsoncInstance.fromTextDocument(document); if (instance.typeOf() === "undefined") { return; } const $schema = instance.get("#/$schema"); - const contextDialectUri = $schema.value(); + const contextDialectUri = $schema.value() ?? settings.defaultDialect; const schemaResources = decomposeSchemaDocument(instance, contextDialectUri); for (const { dialectUri, schemaInstance } of schemaResources) { if (!hasDialect(dialectUri)) { const $schema = schemaInstance.get("#/$schema"); if ($schema.typeOf() === "string") { diagnostics.push(buildDiagnostic($schema, "Unknown dialect")); + } else if (contextDialectUri) { + diagnostics.push(buildDiagnostic(schemaInstance, "Unknown dialect")); } else { diagnostics.push(buildDiagnostic(schemaInstance, "No dialect")); } @@ -260,9 +303,10 @@ const getTokenBuilder = (uri) => { return result; }; -const buildTokens = (builder, document) => { +const buildTokens = (builder, document, settings) => { const instance = JsoncInstance.fromTextDocument(document); - const dialectUri = instance.get("#/$schema").value(); + const $schema = instance.get("#/$schema"); + const dialectUri = $schema.value() ?? settings.defaultDialect; const schemaResources = decomposeSchemaDocument(instance, dialectUri); for (const { keywordInstance, tokenType, tokenModifier } of getSemanticTokens(schemaResources)) { const startPosition = keywordInstance.startPosition(); @@ -276,13 +320,14 @@ const buildTokens = (builder, document) => { } }; -connection.languages.semanticTokens.on(({ textDocument }) => { +connection.languages.semanticTokens.on(async ({ textDocument }) => { connection.console.log(`semanticTokens.on: ${textDocument.uri}`); if (isSchema(textDocument.uri)) { const builder = getTokenBuilder(textDocument.uri); const document = documents.get(textDocument.uri); - buildTokens(builder, document); + const settings = await getDocumentSettings(document.uri); + buildTokens(builder, document, settings); return builder.build(); } else { @@ -290,17 +335,18 @@ connection.languages.semanticTokens.on(({ textDocument }) => { } }); -connection.languages.semanticTokens.onDelta(({ textDocument, previousResultId }) => { +connection.languages.semanticTokens.onDelta(async ({ textDocument, previousResultId }) => { connection.console.log(`semanticTokens.onDelta: ${textDocument.uri}`); const document = documents.get(textDocument.uri); + const settings = await getDocumentSettings(document.uri); if (document === undefined) { return { edits: [] }; } const builder = getTokenBuilder(document); builder.previousResult(previousResultId); - buildTokens(builder, document); + buildTokens(builder, document, settings); return builder.buildEdits(); }); diff --git a/vscode/package.json b/vscode/package.json index 9994357..491b4ec 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -32,5 +32,14 @@ "onLanguage:json" ], "contributes": { + "configuration": { + "title": "Configuration for JSON Schema Language Server", + "properties": { + "jsonSchemaLanguageServer.defaultDialect": { + "type": "string", + "description": "The default JSON Schema dialect to use if none is specified in the schema document" + } + } + } } -} +} \ No newline at end of file