diff --git a/src/configuration.ts b/src/configuration.ts index 1ce5802..8c52848 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -76,17 +76,32 @@ export class Configuration { */ private readonly multiLineLangDefinitionFilePath: string = `${this.autoGeneratedDir}/multi-line-languages.json`; + /** + * The default multi-line configuration. + */ + private readonly defaultMultiLineConfig: vscode.LanguageConfiguration; + + /** + * The languages to skip, like plaintext, that don't have comment syntax. + */ + private readonly languagesToSkip: JsonObject; + /*********** * Methods * ***********/ public constructor() { + const configPath = `${__dirname}/../../config`; + // Read the default multi-line config from the JSON file and cache it for later use. + this.defaultMultiLineConfig = utils.readJsonFile(`${configPath}/default-multi-line-config.json`) as vscode.LanguageConfiguration; + // Read the languages to skip from the JSON file and cache it for later use. + this.languagesToSkip = utils.readJsonFile(`${configPath}/skip-languages.jsonc`); + this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); this.setMultiLineCommentLanguageDefinitions(); this.setSingleLineCommentLanguageDefinitions(); - this.writeCommentLanguageDefinitionsToJsonFile(); this.logDebugInfo(); } @@ -300,8 +315,7 @@ export class Configuration { * @returns {JsonArray} */ private getLanguagesToSkip(): JsonArray { - const json = utils.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`); - return json.languages as JsonArray; + return this.languagesToSkip.languages as JsonArray; } /** @@ -662,24 +676,53 @@ export class Configuration { this.singleLineBlocksMap.set("customSupportedLanguages", new Map([...tempMap].sort())); } + /** + * Get the single-line language definitions object + * formatted and reversed by comment style for + * logging and development debug file output. + * + * @returns {SingleLineLanguageDefinitions} + */ + public getSingleLineLanguageDefinitions(): SingleLineLanguageDefinitions { + return utils.convertMapToReversedObject(this.singleLineBlocksMap); + } + + /** + * Get the multi-line language definitions object + * formatted for logging and development debug file output. + * + * @returns {MultiLineLanguageDefinitions} + */ + public getMultiLineLanguageDefinitions(): MultiLineLanguageDefinitions { + return Object.fromEntries(this.multiLineBlocksMap) as unknown as MultiLineLanguageDefinitions; + } + /** * Write Comment Language Definitions to the respective JSON file: * either multi-line-languages.json, or single-line-languages.json. */ - private writeCommentLanguageDefinitionsToJsonFile() { + public writeCommentLanguageDefinitionsToJsonFile() { // Ensure the auto-generated directory exists. utils.ensureDirExists(this.autoGeneratedDir); - // Convert the singleLineBlocksMap to an object. - const singleLineData = utils.convertMapToReversedObject(this.singleLineBlocksMap); - - const multiLineData = Object.fromEntries(this.multiLineBlocksMap) as unknown as MultiLineLanguageDefinitions; - // Write into the single-line-languages.json file. - utils.writeJsonFile(this.singleLineLangDefinitionFilePath, singleLineData); + utils.writeJsonFile(this.singleLineLangDefinitionFilePath, this.getSingleLineLanguageDefinitions()); // Write into the multi-line-languages.json file. - utils.writeJsonFile(this.multiLineLangDefinitionFilePath, multiLineData); + utils.writeJsonFile(this.multiLineLangDefinitionFilePath, this.getMultiLineLanguageDefinitions()); + } + + /** + * Update language definitions. + */ + public updateLanguageDefinitions() { + // Remove all elements from the current Map, so we can update + // the definitions with an empty Map. + this.singleLineBlocksMap.clear(); + this.multiLineBlocksMap.clear(); + // Update the definitions. + this.setSingleLineCommentLanguageDefinitions(); + this.setMultiLineCommentLanguageDefinitions(); } /** @@ -693,7 +736,7 @@ export class Configuration { * * This method performs the following tasks: * - Retrieves the internal language configuration for the specified language ID. - * - Reads the default multi-line configuration from a JSON file. + * - Uses the cached default multi-line configuration. * - Merges the default multi-line configuration with the internal language configuration if * multiLine is `true`. * - Sets the appropriate comment styles and onEnter rules. @@ -705,14 +748,17 @@ export class Configuration { * with rogue characters being inserted on new lines. */ private setLanguageConfiguration(langId: LanguageId, multiLine?: boolean, singleLineStyle?: SingleLineCommentStyle): vscode.Disposable { - const internalLangConfig: vscode.LanguageConfiguration = this.getLanguageConfig(langId); - const defaultMultiLineConfig = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`) as vscode.LanguageConfiguration; + const internalLangConfig: vscode.LanguageConfiguration | undefined = this.getLanguageConfig(langId); - let langConfig = {...internalLangConfig}; + // Deep-clone the internalLangConfig so modifications never write back + // into the cached `languageConfigs` Map by accident. + let langConfig: vscode.LanguageConfiguration = internalLangConfig + ? structuredClone(internalLangConfig) + : {}; if (multiLine) { langConfig.autoClosingPairs = utils.mergeArraysBy( - defaultMultiLineConfig.autoClosingPairs, + this.defaultMultiLineConfig.autoClosingPairs, internalLangConfig?.autoClosingPairs, "open" ); @@ -725,13 +771,17 @@ export class Configuration { ); // Only assign the default config comments if it doesn't already exist. - // (nullish assignment operator ??=) - langConfig.comments ??= defaultMultiLineConfig.comments; + // Clone the comments to avoid modifying the original object down the line. + langConfig.comments ??= structuredClone(this.defaultMultiLineConfig.comments); // If the default multi-line comments has been overridden for the langId, - // add the overridden multi-line comments to the langConfig. - if (this.isLangIdMultiLineCommentOverridden(langId)) { - langConfig.comments.blockComment[0] = this.getOverriddenMultiLineComment(langId); + // AND the langConfig has a comments key with a blockComment key, then + // update the opening comment style while preserving the ending. + if (this.isLangIdMultiLineCommentOverridden(langId) && langConfig.comments?.blockComment) { + langConfig.comments.blockComment = [ + this.getOverriddenMultiLineComment(langId), + langConfig.comments.blockComment[1] + ]; } /** @@ -1017,16 +1067,10 @@ export class Configuration { logger.debug("The language configs found are:", this.languageConfigs); // Multi-line language definitions. - logger.debug( - "The supported languages for multi-line blocks:", - utils.readJsonFile(this.multiLineLangDefinitionFilePath) - ); + logger.debug("The supported languages for multi-line blocks:", this.getMultiLineLanguageDefinitions()); // Single-line language definitions. - logger.debug( - "The supported languages for single-line blocks:", - utils.readJsonFile(this.singleLineLangDefinitionFilePath) - ); + logger.debug("The supported languages for single-line blocks:", this.getSingleLineLanguageDefinitions()); } /** diff --git a/src/extension.ts b/src/extension.ts index 31b9a69..9de99ed 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -32,6 +32,11 @@ export function activate(context: vscode.ExtensionContext) { const extensionName = extensionData.get("namespace"); const extensionDisplayName = extensionData.get("displayName"); + // In development mode, write language definitions to JSON files for debugging/reference. + if (context.extensionMode !== vscode.ExtensionMode.Production) { + configuration.writeCommentLanguageDefinitionsToJsonFile(); + } + // Store disposables for cleanup const disposables: vscode.Disposable[] = []; let commentBlocksDisposables: vscode.Disposable[] = []; @@ -75,21 +80,39 @@ export function activate(context: vscode.ExtensionContext) { logger.setLogLevel(logLevel); } - // Settings that require an extension host reload when changed. - const reloadRequiredSettings = [ - "disabledLanguages", - "overrideDefaultLanguageMultiLineComments", + /** + * Automatically update (without extension host reload) language definitions and + * reconfigure the comment blocks when any of the following settings are changed. + */ + const languageSettings = [ "multiLineStyleBlocks", "slashStyleBlocks", "hashStyleBlocks", "semicolonStyleBlocks", + "disabledLanguages", + "overrideDefaultLanguageMultiLineComments", ]; - // Settings that require extension host reload - for (const setting of reloadRequiredSettings) { + for (const setting of languageSettings) { if (event.affectsConfiguration(`${extensionName}.${setting}`)) { - showReloadMessage(extensionName, setting); - break; // Only show one reload message at a time + logger.info(`Configuration setting ${extensionName}.${setting} has changed.`); + // Dispose of old comment block configurations to prevent memory leaks + commentBlocksDisposables.forEach((disposable) => disposable.dispose()); + commentBlocksDisposables = []; + + configuration.updateLanguageDefinitions(); + + // In development mode, write updated language definitions to JSON files. + if (context.extensionMode !== vscode.ExtensionMode.Production) { + configuration.writeCommentLanguageDefinitionsToJsonFile(); + } + + commentBlocksDisposables = configuration.configureCommentBlocks(); + disposables.push(...commentBlocksDisposables); + + logger.info("Comment block configurations have been updated."); + + break; // Only update once per change } } }); @@ -132,21 +155,3 @@ export function activate(context: vscode.ExtensionContext) { export function deactivate() { logger.disposeLogger(); } - -/** - * Shows a message prompting the user to reload the extension host. - * @param extensionName The namespace of the extension - * @param settingName The name of the setting that was changed - */ -function showReloadMessage(extensionName: string, settingName: string): void { - vscode.window - .showInformationMessage( - `The ${extensionName}.${settingName} setting has been changed. Please reload the Extension Host to take effect.`, - "Reload" - ) - .then((selection) => { - if (selection === "Reload") { - vscode.commands.executeCommand("workbench.action.restartExtensionHost"); - } - }); -} diff --git a/tsconfig.json b/tsconfig.json index 345fb18..27594a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,5 +8,8 @@ "rootDir": ".", "typeRoots": ["./node_modules/@types"] }, + "typeAcquisition": { + "enable": true, + }, "exclude": ["node_modules", ".vscode-test"] }