diff --git a/language-server/src/features/document-settings.js b/language-server/src/features/document-settings.js index 6eee051..5026798 100644 --- a/language-server/src/features/document-settings.js +++ b/language-server/src/features/document-settings.js @@ -3,8 +3,6 @@ import { publish } from "../pubsub.js"; import { clearSchemaDocuments } from "./schema-documents.js"; -export const isSchema = RegExp.prototype.test.bind(/(?:\.|\/|^)schema\.json$/); - let hasConfigurationCapability = false; let hasDidChangeConfigurationCapability = false; @@ -21,12 +19,10 @@ export default { connection.client.register(DidChangeConfigurationNotification.type); } - connection.onDidChangeConfiguration((change) => { + connection.onDidChangeConfiguration(() => { if (hasConfigurationCapability) { documentSettings.clear(); clearSchemaDocuments(); - } else { - globalSettings = change.settings.jsonSchemaLanguageServer ?? globalSettings; } publish("workspaceChanged", { changes: [] }); @@ -39,19 +35,21 @@ export default { }; const documentSettings = new Map(); -let globalSettings = {}; +const defaultSettings = { + schemaFilePatterns: ["**/*.schema.json", "**/schema.json"] +}; export const getDocumentSettings = async (connection, uri) => { if (!hasConfigurationCapability) { - return globalSettings; + return defaultSettings; } if (!documentSettings.has(uri)) { const result = await connection.workspace.getConfiguration({ scopeUri: uri, section: "jsonSchemaLanguageServer" - }); - documentSettings.set(uri, result ?? globalSettings); + }) ?? {}; + documentSettings.set(uri, { ...defaultSettings, ...result }); } return documentSettings.get(uri); diff --git a/language-server/src/features/semantic-tokens.js b/language-server/src/features/semantic-tokens.js index 96d55b9..08c98d5 100644 --- a/language-server/src/features/semantic-tokens.js +++ b/language-server/src/features/semantic-tokens.js @@ -3,7 +3,9 @@ import { getKeywordId } from "@hyperjump/json-schema/experimental"; import * as Instance from "../json-instance.js"; import { getSchemaDocument } from "./schema-documents.js"; import { toAbsoluteUri } from "../util.js"; -import { isSchema } from "./document-settings.js"; +import { isMatchedFile } from "./workspace.js"; +import { fileURLToPath } from "node:url"; +import { getDocumentSettings } from "./document-settings.js"; export default { @@ -51,7 +53,10 @@ export default { }; connection.languages.semanticTokens.on(async ({ textDocument }) => { - if (!isSchema(textDocument.uri)) { + const filePath = fileURLToPath(textDocument.uri); + const settings = await getDocumentSettings(connection); + const schemaFilePatterns = settings.schemaFilePatterns; + if (!isMatchedFile(filePath, schemaFilePatterns)) { return { data: [] }; } diff --git a/language-server/src/features/workspace.js b/language-server/src/features/workspace.js index abbe4d7..ef28f4c 100644 --- a/language-server/src/features/workspace.js +++ b/language-server/src/features/workspace.js @@ -11,7 +11,8 @@ import { import { TextDocument } from "vscode-languageserver-textdocument"; import { publish, publishAsync, subscribe } from "../pubsub.js"; import { getSchemaDocument } from "./schema-documents.js"; -import { isSchema } from "./document-settings.js"; +import { getDocumentSettings } from "./document-settings.js"; +import picomatch from "picomatch"; let hasWorkspaceFolderCapability = false; @@ -62,7 +63,9 @@ export default { reporter.begin("JSON Schema: Indexing workspace"); // Re/validate all schemas - for await (const uri of workspaceSchemas()) { + const settings = await getDocumentSettings(connection); + const schemaFilePatterns = settings.schemaFilePatterns; + for await (const uri of workspaceSchemas(schemaFilePatterns)) { let textDocument = documents.get(uri); if (!textDocument) { const instanceJson = await readFile(fileURLToPath(uri), "utf8"); @@ -130,7 +133,10 @@ export default { connection.onDidChangeWatchedFiles(onWorkspaceChange); documents.onDidChangeContent(async ({ document }) => { - if (isSchema(document.uri)) { + const settings = await getDocumentSettings(connection); + const schemaFilePatterns = settings.schemaFilePatterns; + const filePath = fileURLToPath(document.uri); + if (isMatchedFile(filePath, schemaFilePatterns)) { validateSchema(document); } }); @@ -139,6 +145,19 @@ export default { } }; +export const isMatchedFile = (uri, patterns) => { + patterns = patterns.map((pattern) => `**/${pattern}`); + const matchers = patterns.map((pattern) => { + return picomatch(pattern, { + noglobstar: false, + matchBase: false, + dot: true, + nonegate: true + }); + }); + return matchers.some((matcher) => matcher(uri)); +}; + const workspaceFolders = new Set(); const addWorkspaceFolders = (folders) => { @@ -159,7 +178,7 @@ const removeWorkspaceFolders = (folders) => { const watchers = {}; -const watchWorkspace = (handler) => { +const watchWorkspace = (handler, schemaFilePatterns) => { for (const { uri } of workspaceFolders) { const path = fileURLToPath(uri); @@ -168,19 +187,19 @@ const watchWorkspace = (handler) => { } watchers[path] = watch(path, { recursive: true }, (eventType, filename) => { - if (isSchema(filename)) { + if (isMatchedFile(filename, schemaFilePatterns)) { handler(eventType, filename); } }); } }; -const workspaceSchemas = async function* () { +const workspaceSchemas = async function* (schemaFilePatterns) { for (const { uri } of workspaceFolders) { const path = fileURLToPath(uri); for (const filename of await readdir(path, { recursive: true })) { - if (isSchema(filename)) { + if (isMatchedFile(filename, schemaFilePatterns)) { const schemaPath = resolve(path, filename); yield pathToFileURL(schemaPath).toString(); diff --git a/package-lock.json b/package-lock.json index 7352576..94ff990 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@hyperjump/json-pointer": "^1.0.1", "@hyperjump/json-schema": "github:hyperjump-io/json-schema#lsp", "@hyperjump/pact": "^1.3.0", - "@hyperjump/uri": "^1.2.2" + "@hyperjump/uri": "^1.2.2", + "picomatch": "^4.0.2" }, "devDependencies": { "@vitest/coverage-v8": "*", @@ -3313,6 +3314,17 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz", diff --git a/package.json b/package.json index c4d2533..02b36a9 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@hyperjump/json-pointer": "^1.0.1", "@hyperjump/json-schema": "github:hyperjump-io/json-schema#lsp", "@hyperjump/pact": "^1.3.0", - "@hyperjump/uri": "^1.2.2" + "@hyperjump/uri": "^1.2.2", + "picomatch": "^4.0.2" } } diff --git a/vscode/package.json b/vscode/package.json index a6450da..f5ca42a 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -38,6 +38,11 @@ "jsonSchemaLanguageServer.defaultDialect": { "type": "string", "description": "The default JSON Schema dialect to use if none is specified in the schema document" + }, + "jsonSchemaLanguageServer.schemaFilePatterns": { + "type": "array", + "description": "The glob pattern for identifying JSON Schema files.", + "default": ["**/*.schema.json", "**/schema.json"] } } }