From 51264facc000aa5f10499f85a0654026588cd11e Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 3 Jul 2025 04:29:08 +0100 Subject: [PATCH 01/48] refactor: extract debug logging from `constructor` into new method - Added the new `logDebugInfo` method to log all debugging information to the Output channel. - Extracted all the debug logging from the `constructor` into it's own method to keep the constructor clean. - Changed the format of the data logged for the supported languages. Instead of logging the `Map`s, we now utilise the auto-generated language definitions files. This is because the single-line languages are reformatted in a human-readable way to output to the file, so it would be better reading this formatted file than the JS `Map`. --- src/configuration.ts | 47 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 9f4ab51..cdeeaad 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -65,21 +65,6 @@ export class Configuration { const extensionVersion = vscode.extensions.getExtension(extensionId)?.packageJSON.version; this.logger.info(`Extension: ${extensionId} (${extensionVersion})`); - const env = { - "OS": process.platform, - "Platform": process.platform, - "VS Code Version": vscode.version, - "VS Code Root Path": vscode.env.appRoot, - "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, - "VS Code Host": vscode.env.appHost, - "VS Code Remote Name": vscode.env.remoteName || "local", - "Other System Env Variables": process.env, - }; - this.logger.debug("Environment:", env); - - // Log the extension's user configuration settings. - this.logger.debug("Configuration settings:", this.getConfiguration()); - this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -87,11 +72,7 @@ export class Configuration { this.setSingleLineCommentLanguageDefinitions(); this.writeCommentLanguageDefinitionsToJsonFile(); - // Log the objects for debugging purposes. - this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); - this.logger.debug("The language configs found are:", this.languageConfigs); - this.logger.debug("The supported languages for multi-line blocks:", this.multiLineBlocksMap); - this.logger.debug("The supported languages single-line blocks:", this.singleLineBlocksMap); + this.logDebugInfo(); } /** @@ -1001,4 +982,30 @@ export class Configuration { this.setBladeComments(false); } } + + /** + * Logs the environment, configuration settings, and language configs for debugging purposes. + */ + private logDebugInfo() { + const env = { + "OS": process.platform, + "Platform": process.platform, + "VS Code Version": vscode.version, + "VS Code Root Path": vscode.env.appRoot, + "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, + "VS Code Host": vscode.env.appHost, + "VS Code Remote Name": vscode.env.remoteName || "local", + "Other System Env Variables": process.env, + }; + this.logger.debug("Environment:", env); + + // Log the extension's user configuration settings. + this.logger.debug("Configuration settings:", this.getConfiguration()); + + // Log the objects for debugging purposes. + this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); + this.logger.debug("The language configs found are:", this.languageConfigs); + this.logger.debug("The supported languages for multi-line blocks:", this.readJsonFile(this.multiLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for single-line blocks:", this.readJsonFile(this.singleLineLangDefinitionFilePath)); + } } From e057edafd3358eaf38d51d20ce20a306ab2768a7 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 9 Jul 2025 19:02:48 +0100 Subject: [PATCH 02/48] build: add new dependency to detect if WSL is being used. --- package.json | 1 + src/configuration.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index f3c75be..70511f3 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "typescript": "^5.7" }, "dependencies": { + "is-wsl": "^3.1.0", "jsonc-parser": "^3.3.1" } } diff --git a/src/configuration.ts b/src/configuration.ts index cdeeaad..6322ffb 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode"; import * as fs from "node:fs"; import * as jsonc from "jsonc-parser"; import * as path from "path"; +import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; From 6d6e6f1c1ec0f82bd006c67c41286a82f5a78e16 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 02:01:59 +0100 Subject: [PATCH 03/48] feat: add the ability to get all Windows extensions when running on WSL. This is a work around for the fact that the `vscode.extensions.all` API can't find any built-in extensions on the Windows-side when running in WSL. It only gets the WSL-installed extensions, which caused the extension not to work in yCodeTech/auto-comment-blocks#6 There is an open issue and a proposed API change in the sourcecode since 2022, but hasn't been released as a public API. See https://github.com/microsoft/vscode/issues/145307 for more info. Added: - Added new `getExtensionsPathsFromWslEnv` method to retrieve the Windows built-in and user extensions paths from the environment variables when running in WSL. - Added new `readExtensionsFromDirectory` method to read the extensions paths and retrieve each extension data. Changed: - Changed `findAllLanguageConfigFilePaths` method to check if the extension is running in WSL. If it `isWsl`, then it calls the new `getExtensionsPathsFromWslEnv` method to get the paths, and uses the new `readExtensionsFromDirectory` to obtain the extensions data. We construct a new `extensions` array with all the windows-installed built-in and user extensions and also the WSL-installed extensions via `vscode.extensions.all`. (The extension will still work on Windows without WSL due to it using `vscode.extensions.all` outside of the `isWsl` check.) - Changed the `for...of` loop to iterate through the new `extensions` array. --- src/configuration.ts | 90 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 6322ffb..0791807 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -304,8 +304,27 @@ export class Configuration { * (built-in and 3rd party). */ private findAllLanguageConfigFilePaths() { + const extensions: any[] = []; + + // If running in WSL... + if (isWsl) { + // Get the Windows built-in and user extensions paths from WSL environment variables. + const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + + // Read the paths and create arrays of the extensions. + const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPathFromWsl); + const userExtensions = this.readExtensionsFromDirectory(userExtensionsPathFromWsl); + + // Combine the built-in and user extensions into the extensions array. + extensions.push(...builtInExtensions, ...userExtensions); + } + + // Add all installed extensions (including built-in ones) into the extensions array. + // If running WSL, these will be the WSL-installed extensions. + extensions.push(...vscode.extensions.all); + // Loop through all installed extensions, including built-in extensions - for (let extension of vscode.extensions.all) { + for (let extension of extensions) { const packageJSON = extension.packageJSON; // If an extension package.json has "contributes" key, @@ -425,6 +444,41 @@ export class Configuration { fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); } + /** + * Read the directory in the given path and return an array of objects with the data of + * all extensions found in the directory. + * + * @param {string} extensionsPath The path where extensions are stored. + * + * @returns {Array<{ id: string; extensionPath: string; packageJSON: any }>} + */ + private readExtensionsFromDirectory(extensionsPath: string): Array<{id: string; extensionPath: string; packageJSON: any}> { + // Create an array to hold the found extensions. + const foundExtensions: Array<{id: string; extensionPath: string; packageJSON: any}> = []; + + fs.readdirSync(extensionsPath).forEach((extensionName) => { + const extensionPath = path.join(extensionsPath, extensionName); + + // If the extensionName is a directory... + if (fs.statSync(extensionPath).isDirectory()) { + // Get the package.json file path. + const packageJSONPath = path.join(extensionPath, "package.json"); + + // If the package.json file exists... + if (fs.existsSync(packageJSONPath)) { + const packageJSON = this.readJsonFile(packageJSONPath); + + const id = `${packageJSON.publisher}.${packageJSON.name}`; + + // Push the extension data object into the array. + foundExtensions.push({id, extensionPath, packageJSON}); + } + } + }); + + return foundExtensions; + } + /** * Get the multi-line languages from the Map. * @@ -984,6 +1038,40 @@ export class Configuration { } } + /** + * Gets the user and built-in extensions paths on Windows from WSL environment variables. + * + * @returns {Object} An object containing the user and built-in extensions paths. + */ + private getExtensionsPathsFromWslEnv() { + const extensionNames = this.getExtensionNames(); + + /** Built-in Extensions */ + + // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. + // The variable will be a path like: + // "/mnt/c/Users/USERNAME/AppData/Local/Programs/Microsoft VS Code". + const vscodePathFromWsl = process.env.VSCODE_CWD; + // Append "resources/app/extensions" to the base path to form the path to the + // built-in extensions. + const builtInExtensionsPathFromWsl = path.join(vscodePathFromWsl, "resources/app/extensions"); + + /** User Extensions */ + + // Get the user extensions path from VSCODE_WSL_EXT_LOCATION env variable. + // If it's not set, then use an empty string. + // The variable will be a path like: + // "/mnt/c/Users/USERNAME/.vscode/extensions/ms-vscode-remote.remote-wsl-0.99.0". + // So we just need to return the directory name like: + // "/mnt/c/Users/USERNAME/.vscode/extensions/" + const userExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); + + return { + builtInExtensionsPathFromWsl, + userExtensionsPathFromWsl, + }; + } + /** * Logs the environment, configuration settings, and language configs for debugging purposes. */ From c97dbdb5ab3145e7046e564bf95fb8a3d5ce4068 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 02:10:45 +0100 Subject: [PATCH 04/48] refactor: `logDebugInfo` method to log different stuff when on WSL. Added: - Added an `isWsl` check, and uses the new `getExtensionsPathsFromWslEnv` method to log the Windows extensions paths from WSL. Changed: - Refactored all "VS Code" items into a new object so that we're not repeating "VS Code" multiple times. This makes for a better reading experience. --- src/configuration.ts | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 0791807..3118733 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1076,14 +1076,38 @@ export class Configuration { * Logs the environment, configuration settings, and language configs for debugging purposes. */ private logDebugInfo() { + // The path to the built-in extensions. The env variable changes when on WSL. + // So we can use it for both Windows and WSL. + const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); + + let extensionsPaths = {}; + + if (isWsl) { + // Get the Windows user and built-in extensions paths on Windows from WSL environment variables. + const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + + extensionsPaths = { + "Windows-installed Built-in Extensions Path": builtInExtensionsPathFromWsl, + "Windows-installed User Extensions Path": userExtensionsPathFromWsl, + "WSL-installed Built-in Extensions Path": builtInExtensionsPath, + "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), + }; + } else { + extensionsPaths = { + "Built-in Extensions Path": builtInExtensionsPath, + "User Extensions Path": path.join(process.env.USERPROFILE, ".vscode", "extensions"), + }; + } + const env = { "OS": process.platform, "Platform": process.platform, - "VS Code Version": vscode.version, - "VS Code Root Path": vscode.env.appRoot, - "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, - "VS Code Host": vscode.env.appHost, - "VS Code Remote Name": vscode.env.remoteName || "local", + "VS Code Details": { + "Version": vscode.version, + "Remote Name": vscode.env.remoteName || "local", + "Host": vscode.env.appHost, + ...extensionsPaths, + }, "Other System Env Variables": process.env, }; this.logger.debug("Environment:", env); From 3fea208197f9e78aa724b2d051697e444b66701e Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 03:01:57 +0100 Subject: [PATCH 05/48] fix: keybindings, docs, and paths to enable usage on macOS. --- README.md | 6 +++--- package.json | 5 +++-- src/configuration.ts | 5 ++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 03e0a91..656cf7f 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Use `/*!` in all file types that support the normal `/*` comments to start a QDo #### Normal comment blocks -Using the normal comment block `/* */` either typing manually or the native VScode command "Toggle Block Comment" (`editor.action.blockComment`, native keybinding `shift + alt + a`), the block will have the same on enter functionality as described above. +Using the normal comment block `/* */` either typing manually or the native VScode command "Toggle Block Comment" (`editor.action.blockComment`, native keybinding shift + alt + a (macOS: shift + option + a)), the block will have the same on enter functionality as described above. ![block-comments](https://raw.githubusercontent.com/yCodeTech/auto-comment-blocks/master/img/block-comments.gif) @@ -156,9 +156,9 @@ Reload the extension after changing any settings. - `auto-comment-blocks.multiLineStyleBlocks`: Add language IDs here to enable multi-line comment blocks support for that language, allowing unsupported languages to have comment completion. The default is `['blade', 'html']`" -- `auto-comment-blocks.overrideDefaultLanguageMultiLineComments`: A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode "Toggle Block Comment" `editor.action.blockComment` command (native Keybinding `shift + alt + a`). eg. `{'php': '/*!'}` +- `auto-comment-blocks.overrideDefaultLanguageMultiLineComments`: A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode "Toggle Block Comment" `editor.action.blockComment` command (native Keybinding shift + alt + a (macOS: shift + option + a)). eg. `{'php': '/*!'}` -- `auto-comment-blocks.bladeOverrideComments`: When enabled, Blade-style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m`. If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used. +- `auto-comment-blocks.bladeOverrideComments`: When enabled, Blade-style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default ctrl + shift + m (macOS: cmd + shift + m). If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used. ## Known Issues diff --git a/package.json b/package.json index 70511f3..5aaf61d 100644 --- a/package.json +++ b/package.json @@ -71,13 +71,13 @@ "auto-comment-blocks.overrideDefaultLanguageMultiLineComments": { "type": "object", "default": {}, - "markdownDescription": "A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode `command editor.action.blockComment` (native Keybinding `shift + alt + a`). eg. `{'php': '/*!'}`" + "markdownDescription": "A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode `command editor.action.blockComment` (native Keybinding `shift + alt + a` (macOS: `shift + option + a`)). eg. `{'php': '/*!'}`" }, "auto-comment-blocks.bladeOverrideComments": { "scope": "resource", "type": "boolean", "default": false, - "markdownDescription": "When enabled, Blade style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m`. If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used." + "markdownDescription": "When enabled, Blade style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m` (macOS: `cmd + shift + m`). If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used." } } }, @@ -90,6 +90,7 @@ { "command": "auto-comment-blocks.changeBladeMultiLineBlock", "key": "ctrl+shift+m", + "mac": "cmd+shift+m", "when": "editorTextFocus" } ] diff --git a/src/configuration.ts b/src/configuration.ts index 3118733..679efe6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1093,9 +1093,12 @@ export class Configuration { "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), }; } else { + // Get the user home directory from the environment variable: + // `USERPROFILE` on Windows, `HOME` on macOS/Linux. + const userHome = process.env.USERPROFILE || process.env.HOME; extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": path.join(process.env.USERPROFILE, ".vscode", "extensions"), + "User Extensions Path": path.join(userHome, ".vscode", "extensions"), }; } From ff8e4f5b9abbf90d3d01361dbfe90b3e98600e00 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 05:59:34 +0100 Subject: [PATCH 06/48] refactor: create a uniform way of obtaining this extension's details. Added: - Added new `extensionDetails` private property in `Configuration` as a Map object. - Added new `setExtensionData` method to set the data into the `extensionDetails` Map. Also added its method call into the `constructor`. - Added new `getExtensionData` public method to get the value of a specified key from the `extensionDetails` Map. Changed: - Refactored `getExtensionNames` to be more generic. This will be used in the new `setExtensionData` method to get and set the names, id and version into the `extensionDetails` Map. - Changed the name to `getExtensionPackageJsonData`. - Changed it's visibility to `private`. - Changed the reading of the package.json file to use the `readJsonFile` method as it does the exact same thing, and makes it DRYer. - Added the extension's version. - Changed all references to the old `getExtensionNames` method to use the new `getExtensionData` method with the specific key needed. Removed: - Removed the unused `extensionNames` variable and method call from the `getExtensionsPathsFromWslEnv` method. --- src/configuration.ts | 65 +++++++++++++++++++++++++++++++++++--------- src/extension.ts | 6 ++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 679efe6..b36b07d 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -22,6 +22,11 @@ export class Configuration { */ private logger: Logger; + /** + * This extension details in the form of a key:value Map object, for ease of use. + */ + private extensionDetails = new Map(); + /** * A key:value Map object of language IDs and their config file paths. */ @@ -61,10 +66,12 @@ export class Configuration { public constructor(logger: Logger) { this.logger = logger; + this.setExtensionData(); + // Always output extension information to channel on activate. - const extensionId = this.getExtensionNames().id; - const extensionVersion = vscode.extensions.getExtension(extensionId)?.packageJSON.version; - this.logger.info(`Extension: ${extensionId} (${extensionVersion})`); + const id = this.getExtensionData("id"); + const version = this.getExtensionData("version"); + this.logger.info(`Extension: ${id} (${version})`); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -194,22 +201,56 @@ export class Configuration { } /** - * Get the names and ids of this extension from package.json. + * Get the names, id, and version of this extension from package.json. * - * @returns {object} An object containing the extension id, name, and display name. + * @returns {object} An object containing the extension id, name, display name, and version. */ - public getExtensionNames(): {id: string; name: string; displayName: string} { - const packageJSON = JSON.parse(fs.readFileSync(__dirname + "/../../package.json").toString()); + private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { + const packageJSON = this.readJsonFile(__dirname + "/../../package.json"); const displayName: string = packageJSON.displayName; const fullname: string = packageJSON.name; const id: string = `${packageJSON.publisher}.${fullname}`; + const version: string = packageJSON.version; let nameParts = fullname.split("-"); nameParts[0] = "auto"; const name = nameParts.join("-"); - return {id: id, name: name, displayName: displayName}; + return {id: id, name: name, displayName: displayName, version: version}; + } + + /** + * Set the extension data into the extensionDetails Map. + */ + private setExtensionData() { + const extensionPackageJsonData = this.getExtensionPackageJsonData(); + + const id = extensionPackageJsonData.id; + const name = extensionPackageJsonData.name; + const displayName = extensionPackageJsonData.displayName; + const version = extensionPackageJsonData.version; + + const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + this.extensionDetails.set("id", id); + this.extensionDetails.set("name", name); + this.extensionDetails.set("displayName", displayName); + this.extensionDetails.set("version", version); + this.extensionDetails.set("userExtensionsPath", userExtensionsPath); + } + + /** + * Get the extension's details. + * + * @param {string} key The key of the specific extension detail to get. + * + * @returns {any} Returns a value of a specific key. + */ + public getExtensionData(key: string): any { + if (this.extensionDetails.has(key)) { + return this.extensionDetails.get(key); + } } /** @@ -218,7 +259,7 @@ export class Configuration { * @returns {vscode.WorkspaceConfiguration} */ public getConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration(this.getExtensionNames().name, null); + return vscode.workspace.getConfiguration(this.getExtensionData("name"), null); } /** @@ -1005,7 +1046,7 @@ export class Configuration { */ private handleChangeBladeMultiLineBlock(textEditor: vscode.TextEditor) { let langId = textEditor.document.languageId; - const extensionNames = this.getExtensionNames(); + const extensionName = this.getExtensionData("name"); // Only carry out function if languageId is blade. if (langId === "blade" && !this.isLangIdDisabled(langId)) { @@ -1029,7 +1070,7 @@ export class Configuration { // then output a message to the user. else if (langId == "blade" && this.isLangIdDisabled(langId)) { vscode.window.showInformationMessage( - `Blade is set as disabled in the "${extensionNames.name}.disabledLanguages" setting. The "${extensionNames.name}.bladeOverrideComments" setting will have no affect.`, + `Blade is set as disabled in the "${extensionName}.disabledLanguages" setting. The "${extensionName}.bladeOverrideComments" setting will have no affect.`, "OK" ); @@ -1044,8 +1085,6 @@ export class Configuration { * @returns {Object} An object containing the user and built-in extensions paths. */ private getExtensionsPathsFromWslEnv() { - const extensionNames = this.getExtensionNames(); - /** Built-in Extensions */ // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. diff --git a/src/extension.ts b/src/extension.ts index 87da677..20eefd1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,10 +17,8 @@ export function activate(context: vscode.ExtensionContext) { disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); - const extensionNames = configuration.getExtensionNames(); - - const extensionName = extensionNames.name; - const extensionDisplayName = extensionNames.displayName; + const extensionName = configuration.getExtensionData("name"); + const extensionDisplayName = configuration.getExtensionData("displayName"); let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); From 14717e651f77135469f408539d4e9bfb608ec93e Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 06:16:23 +0100 Subject: [PATCH 07/48] fix: `logDebugInfo` user extensions path to use `getExtensionData`. Previously we manually created the user extensions path from the home directory env variables and concatenated strings on to it. This is only good if the default extensions directory is used. This will fail if the user has changed the directory location with the CLI `code --extensions-dir ` So to fix, we're now getting the user extensions path from where ever this extension is located. The code for this is defined in `setExtensionData` method as committed in 25fab48. - Changed the construction of the users extensions path in `logDebugInfo` to use the `getExtensionData` method to get the path. - Removed the unneeded home directory variables. --- src/configuration.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index b36b07d..615fbd5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1132,12 +1132,9 @@ export class Configuration { "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), }; } else { - // Get the user home directory from the environment variable: - // `USERPROFILE` on Windows, `HOME` on macOS/Linux. - const userHome = process.env.USERPROFILE || process.env.HOME; extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": path.join(userHome, ".vscode", "extensions"), + "User Extensions Path": this.getExtensionData("userExtensionsPath"), }; } From a658abdefb7efedac4e25abed1a1656496b19875 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 06:24:45 +0100 Subject: [PATCH 08/48] feat: set the built-in extensions path into `extensionDetails` - Moved the code to get the built-in extensions path from `logDebugInfo` in to the `setExtensionData` method. - Added the built-in extensions path to the `extensionDetails` Map in `setExtensionData` method. - Updated `logDebugInfo` method to get the `builtInExtensionsPath` key from the Map using `getExtensionData` method. --- src/configuration.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 615fbd5..2857df1 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -232,12 +232,14 @@ export class Configuration { const version = extensionPackageJsonData.version; const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); this.extensionDetails.set("id", id); this.extensionDetails.set("name", name); this.extensionDetails.set("displayName", displayName); this.extensionDetails.set("version", version); this.extensionDetails.set("userExtensionsPath", userExtensionsPath); + this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); } /** @@ -1117,7 +1119,7 @@ export class Configuration { private logDebugInfo() { // The path to the built-in extensions. The env variable changes when on WSL. // So we can use it for both Windows and WSL. - const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); + const builtInExtensionsPath = this.getExtensionData("builtInExtensionsPath"); let extensionsPaths = {}; From 140cba1f2ba7e95419308b1807d0db40ab4bf546 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:01:08 +0100 Subject: [PATCH 09/48] change: extension logger info to log the `extensionDetails` Map instead. --- src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 2857df1..a36ffb4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -71,7 +71,7 @@ export class Configuration { // Always output extension information to channel on activate. const id = this.getExtensionData("id"); const version = this.getExtensionData("version"); - this.logger.info(`Extension: ${id} (${version})`); + this.logger.debug(`Extension details:`, this.extensionDetails); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); From f46d01d04d9ca12a0286cd7d2bca6fa07e264571 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:20:16 +0100 Subject: [PATCH 10/48] feat: add WSL paths to `setExtensionData` method. Added: - Added the WSL built-in and user extensions paths code to the `setExtensionData` method and set them into the `extensionDetails` with new Map keys. - Moved the environment variable code to get the WSL-installed user extensions path from the `logDebugInfo` method, and added the `getExtensionData` method call to the log item instead. - Refactored the `getExtensionsPathsFromWslEnv` method into `setExtensionData` method, simplifying the code. Removed: - Removed the now unused `getExtensionsPathsFromWslEnv` method, and changed all references to use the `getExtensionData` with the appropriate key. --- src/configuration.ts | 72 ++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index a36ffb4..862512b 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -231,7 +231,13 @@ export class Configuration { const displayName = extensionPackageJsonData.displayName; const version = extensionPackageJsonData.version; - const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + // The path to the user extensions. + const userExtensionsPath = isWSL + ? path.join(vscode.env.appRoot, "../../", "extensions") + : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + // The path to the built-in extensions. + // This env variable changes when on WSL to it's WSL-built-in extensions path. const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); this.extensionDetails.set("id", id); @@ -240,6 +246,18 @@ export class Configuration { this.extensionDetails.set("version", version); this.extensionDetails.set("userExtensionsPath", userExtensionsPath); this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); + + if (isWsl) { + // Get the root path to VS Code from the env variable, and use it to get + // the Windows built-in extensions. + const windowsBuiltInExtensionsPathFromWsl = path.join(process.env.VSCODE_CWD, "resources/app/extensions"); + + // Get the Windows user extensions path from env variable. + const windowsUserExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); + + this.extensionDetails.set("WindowsUserExtensionsPathFromWsl", windowsUserExtensionsPathFromWsl); + this.extensionDetails.set("WindowsBuiltInExtensionsPathFromWsl", windowsBuiltInExtensionsPathFromWsl); + } } /** @@ -351,12 +369,13 @@ export class Configuration { // If running in WSL... if (isWsl) { - // Get the Windows built-in and user extensions paths from WSL environment variables. - const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + // Get the Windows user and built-in extensions paths. + const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); // Read the paths and create arrays of the extensions. - const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPathFromWsl); - const userExtensions = this.readExtensionsFromDirectory(userExtensionsPathFromWsl); + const builtInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); + const userExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); // Combine the built-in and user extensions into the extensions array. extensions.push(...builtInExtensions, ...userExtensions); @@ -1081,38 +1100,6 @@ export class Configuration { } } - /** - * Gets the user and built-in extensions paths on Windows from WSL environment variables. - * - * @returns {Object} An object containing the user and built-in extensions paths. - */ - private getExtensionsPathsFromWslEnv() { - /** Built-in Extensions */ - - // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. - // The variable will be a path like: - // "/mnt/c/Users/USERNAME/AppData/Local/Programs/Microsoft VS Code". - const vscodePathFromWsl = process.env.VSCODE_CWD; - // Append "resources/app/extensions" to the base path to form the path to the - // built-in extensions. - const builtInExtensionsPathFromWsl = path.join(vscodePathFromWsl, "resources/app/extensions"); - - /** User Extensions */ - - // Get the user extensions path from VSCODE_WSL_EXT_LOCATION env variable. - // If it's not set, then use an empty string. - // The variable will be a path like: - // "/mnt/c/Users/USERNAME/.vscode/extensions/ms-vscode-remote.remote-wsl-0.99.0". - // So we just need to return the directory name like: - // "/mnt/c/Users/USERNAME/.vscode/extensions/" - const userExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); - - return { - builtInExtensionsPathFromWsl, - userExtensionsPathFromWsl, - }; - } - /** * Logs the environment, configuration settings, and language configs for debugging purposes. */ @@ -1124,14 +1111,15 @@ export class Configuration { let extensionsPaths = {}; if (isWsl) { - // Get the Windows user and built-in extensions paths on Windows from WSL environment variables. - const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + // Get the Windows user and built-in extensions paths. + const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); extensionsPaths = { - "Windows-installed Built-in Extensions Path": builtInExtensionsPathFromWsl, - "Windows-installed User Extensions Path": userExtensionsPathFromWsl, + "Windows-installed Built-in Extensions Path": windowsBuiltInExtensionsPath, + "Windows-installed User Extensions Path": windowsUserExtensionsPath, "WSL-installed Built-in Extensions Path": builtInExtensionsPath, - "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), + "WSL-installed User Extensions Path": this.getExtensionData("userExtensionsPath"), }; } else { extensionsPaths = { From 9bb5ebb202beca04dda9a37063aec75732a3c72a Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:22:39 +0100 Subject: [PATCH 11/48] fix: `readExtensionsFromDirectory` to skip directories starting with a dot. --- src/configuration.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/configuration.ts b/src/configuration.ts index 862512b..bf7f918 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -523,6 +523,11 @@ export class Configuration { // If the extensionName is a directory... if (fs.statSync(extensionPath).isDirectory()) { + // If the extensionName starts with a dot, skip it. + if (extensionName.startsWith(".")) { + return; + } + // Get the package.json file path. const packageJSONPath = path.join(extensionPath, "package.json"); From 8790f8427ba4d4c325673e5bcf33b088a095b007 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 12 Jul 2025 01:06:51 +0100 Subject: [PATCH 12/48] fix: TS compile error: Cannot find name 'isWSL'. Did you mean 'isWsl'? https://github.com/yCodeTech/auto-comment-blocks/actions/runs/16210470528 --- src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index bf7f918..de1cdf2 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -232,7 +232,7 @@ export class Configuration { const version = extensionPackageJsonData.version; // The path to the user extensions. - const userExtensionsPath = isWSL + const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); From 1ecabcced9924070071dbe097a2043bc2e8cd5e9 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 16 Jul 2025 02:25:13 +0100 Subject: [PATCH 13/48] remove: unused variables which the usages were removed in commit 140cba1 --- src/configuration.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index de1cdf2..112ad9f 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -69,8 +69,6 @@ export class Configuration { this.setExtensionData(); // Always output extension information to channel on activate. - const id = this.getExtensionData("id"); - const version = this.getExtensionData("version"); this.logger.debug(`Extension details:`, this.extensionDetails); this.findAllLanguageConfigFilePaths(); From f15cfe012deeeed2a05dc085e2b2f2e56ddbdffb Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 02:36:56 +0100 Subject: [PATCH 14/48] refactor: move utility functions to a new `utils` file. - Moved utility functions to a new `utils` file for better organisation and reusability between files. - `readJsonFile` - `writeJsonFile` - `reconstructRegex` - `convertMapToReversedObject` - Changed all references to these functions to use the new namespace `utils` import. --- src/configuration.ts | 159 ++++++------------------------------------- src/utils.ts | 122 +++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 139 deletions(-) create mode 100644 src/utils.ts diff --git a/src/configuration.ts b/src/configuration.ts index 112ad9f..60ca232 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -3,12 +3,12 @@ import * as vscode from "vscode"; import * as fs from "node:fs"; -import * as jsonc from "jsonc-parser"; import * as path from "path"; import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; +import * as utils from "./utils"; export class Configuration { /************** @@ -204,7 +204,7 @@ export class Configuration { * @returns {object} An object containing the extension id, name, display name, and version. */ private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { - const packageJSON = this.readJsonFile(__dirname + "/../../package.json"); + const packageJSON = utils.readJsonFile(__dirname + "/../../package.json"); const displayName: string = packageJSON.displayName; const fullname: string = packageJSON.name; @@ -354,7 +354,7 @@ export class Configuration { * @returns {string[]} */ private getLanguagesToSkip(): string[] { - const json = this.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`); + const json = utils.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`); return json.languages; } @@ -417,7 +417,7 @@ export class Configuration { */ private setLanguageConfigDefinitions() { this.languageConfigFilePaths.forEach((filepath, langId) => { - const config = this.readJsonFile(filepath); + const config = utils.readJsonFile(filepath); // If the config JSON has more than 0 keys (ie. not empty) if (Object.keys(config).length > 0) { @@ -475,35 +475,6 @@ export class Configuration { } } - /** - * Read the file and parse the JSON. - * - * @param {string} filepath The path of the file. - * @returns The file content. - */ - private readJsonFile(filepath: string): any { - return jsonc.parse(fs.readFileSync(filepath).toString()); - } - - /** - * Read the file and parse the JSON. - * - * @param {string} filepath The path of the file. - * @param {any} data The data to write into the file. - * @returns The file content. - */ - private writeJsonFile(filepath: string, data: any): any { - // Check if the "auto-generated-language-definitions" directory exists, - // and create it if it doesn't. - if (!fs.existsSync(this.autoGeneratedDir)) { - fs.mkdirSync(this.autoGeneratedDir); - } - - // Write the updated JSON back into the file and add tab indentation - // to make it easier to read. - fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); - } - /** * Read the directory in the given path and return an array of objects with the data of * all extensions found in the directory. @@ -531,7 +502,7 @@ export class Configuration { // If the package.json file exists... if (fs.existsSync(packageJSONPath)) { - const packageJSON = this.readJsonFile(packageJSONPath); + const packageJSON = utils.readJsonFile(packageJSONPath); const id = `${packageJSON.publisher}.${packageJSON.name}`; @@ -692,9 +663,9 @@ export class Configuration { */ private writeCommentLanguageDefinitionsToJsonFile() { // Write the into the single-line-languages.json file. - this.writeJsonFile(this.singleLineLangDefinitionFilePath, this.convertMapToReversedObject(this.singleLineBlocksMap)); + utils.writeJsonFile(this.singleLineLangDefinitionFilePath, utils.convertMapToReversedObject(this.singleLineBlocksMap)); // Write the into the multi-line-languages.json file. - this.writeJsonFile(this.multiLineLangDefinitionFilePath, Object.fromEntries(this.multiLineBlocksMap)); + utils.writeJsonFile(this.multiLineLangDefinitionFilePath, Object.fromEntries(this.multiLineBlocksMap)); } /** @@ -721,7 +692,7 @@ export class Configuration { */ private setLanguageConfiguration(langId: string, multiLine?: boolean, singleLineStyle?: string): vscode.Disposable { const internalLangConfig: vscode.LanguageConfiguration = this.getLanguageConfig(langId); - const defaultMultiLineConfig: any = this.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); + const defaultMultiLineConfig: any = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); let langConfig = {...internalLangConfig}; @@ -798,15 +769,15 @@ export class Configuration { langConfig.onEnterRules.forEach((item) => { // Check if the item has a "beforeText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "beforeText")) { - item.beforeText = this.reconstructRegex(item, "beforeText"); + item.beforeText = utils.reconstructRegex(item, "beforeText"); } // Check if the item has an "afterText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "afterText")) { - item.afterText = this.reconstructRegex(item, "afterText"); + item.afterText = utils.reconstructRegex(item, "afterText"); } // Check if the item has an "afterText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "previousLineText")) { - item.previousLineText = this.reconstructRegex(item, "previousLineText"); + item.previousLineText = utils.reconstructRegex(item, "previousLineText"); } }); } @@ -822,17 +793,17 @@ export class Configuration { // If langConfig has a wordPattern key... if (Object.hasOwn(langConfig, "wordPattern")) { - langConfig.wordPattern = this.reconstructRegex(langConfig, "wordPattern"); + langConfig.wordPattern = utils.reconstructRegex(langConfig, "wordPattern"); } // If langConfig has a folding key... if (Object.hasOwn(langConfig, "folding")) { // @ts-ignore error TS2339: Property 'folding' does not exist on type if (Object.hasOwn(langConfig.folding, "markers")) { // @ts-ignore error TS2339: Property 'folding' does not exist on type - langConfig.folding.markers.start = this.reconstructRegex(langConfig.folding.markers, "start"); + langConfig.folding.markers.start = utils.reconstructRegex(langConfig.folding.markers, "start"); // @ts-ignore error TS2339: Property 'folding' does not exist on type - langConfig.folding.markers.end = this.reconstructRegex(langConfig.folding.markers, "end"); + langConfig.folding.markers.end = utils.reconstructRegex(langConfig.folding.markers, "end"); } } // If langConfig has a indentationRules key... @@ -843,19 +814,19 @@ export class Configuration { for (let key in indentationRules) { // If the key is "increaseIndentPattern", reconstruct the regex pattern. if (key === "increaseIndentPattern") { - indentationRules.increaseIndentPattern = this.reconstructRegex(indentationRules, "increaseIndentPattern"); + indentationRules.increaseIndentPattern = utils.reconstructRegex(indentationRules, "increaseIndentPattern"); } // If the key is "decreaseIndentPattern", reconstruct the regex pattern. if (key === "decreaseIndentPattern") { - indentationRules.decreaseIndentPattern = this.reconstructRegex(indentationRules, "decreaseIndentPattern"); + indentationRules.decreaseIndentPattern = utils.reconstructRegex(indentationRules, "decreaseIndentPattern"); } // If the key is "indentNextLinePattern", reconstruct the regex pattern. if (key === "indentNextLinePattern") { - indentationRules.indentNextLinePattern = this.reconstructRegex(indentationRules, "indentNextLinePattern"); + indentationRules.indentNextLinePattern = utils.reconstructRegex(indentationRules, "indentNextLinePattern"); } // If the key is "unIndentedLinePattern", reconstruct the regex pattern. if (key === "unIndentedLinePattern") { - indentationRules.unIndentedLinePattern = this.reconstructRegex(indentationRules, "unIndentedLinePattern"); + indentationRules.unIndentedLinePattern = utils.reconstructRegex(indentationRules, "unIndentedLinePattern"); } } } @@ -914,96 +885,6 @@ export class Configuration { return merged; } - /** - * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, - * or some patterns are not working as expected. - * - * @param obj The object - * @param key The key to check in the object - * @returns {RegExp} The reconstructed regex pattern. - */ - private reconstructRegex(obj: any, key: string) { - // If key has a "pattern" key, then it's an object... - if (Object.hasOwn(obj[key], "pattern")) { - return new RegExp(obj[key].pattern); - } - // Otherwise it's a string. - else { - return new RegExp(obj[key]); - } - } - - /** - * Convert a Map to an object with it's inner Map's keys and values reversed/switched. - * - * Code based on this StackOverflow answer https://stackoverflow.com/a/45728850/2358222 - * - * @param {Map>} m The Map to convert to an object. - * @returns {object} The converted object. - * - * @example - * reverseMapping( - * Map { - * "supportedLanguages" => Map { - * "apacheconf" => "#", - * "c" => "//", - * "clojure" => ";", - * "coffeescript" => "#", - * "cpp" => "//", - * … - * } - * } - * ); - * - * // Converts to: - * - * { - * "supportedLanguages" => { - * "#": [ - * "apacheconf", - * "coffeescript", - * ... - * ], - * "//": [ - * "c", - * "cpp", - * ... - * ], - * ";": [ - * "clojure", - * ... - * ] - * } - * } - */ - private convertMapToReversedObject(m: Map>): object { - const result: any = {}; - - // Convert a nested key:value Map from inside another Map into an key:array object, - // while reversing/switching the keys and values. The Map's values are now the keys of - // the object and the Map's keys are now added as the values of the array. The reversed - // object is added to the key of the outerMap. - - // Loop through the outer Map... - for (const [key, innerMap] of m.entries()) { - // Convert the inner Map to an object - const o = Object.fromEntries(innerMap); - - // Reverse the inner object mapping. - // - // Loop through the object (o) keys, assigns a new object (r) with the value of the - // object key (k) as the new key (eg. "//") and the new value is an array of all - // the original object keys (o[k]) (eg. "php"). - // If the key (o[k]) already exists in the new object (r), then just add the - // original key to the array, otherwise start a new array ([]) with the original - // key as value ( (r[o[k]] || []).concat(k) ). - // Add this new reversed object to the result object with the outer map key - // as the key. - result[key] = Object.keys(o).reduce((r, k) => Object.assign(r, {[o[k]]: (r[o[k]] || []).concat(k)}), {}); - } - return result; - } - /** * The keyboard binding event handler for the single-line blocks on shift+enter. * @@ -1150,7 +1031,7 @@ export class Configuration { // Log the objects for debugging purposes. this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); this.logger.debug("The language configs found are:", this.languageConfigs); - this.logger.debug("The supported languages for multi-line blocks:", this.readJsonFile(this.multiLineLangDefinitionFilePath)); - this.logger.debug("The supported languages for single-line blocks:", this.readJsonFile(this.singleLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for multi-line blocks:", utils.readJsonFile(this.multiLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for single-line blocks:", utils.readJsonFile(this.singleLineLangDefinitionFilePath)); } } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2b124d9 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,122 @@ +import * as fs from "node:fs"; +import * as jsonc from "jsonc-parser"; + +/** + * Read the file and parse the JSON. + * + * @param {string} filepath The path of the file. + * + * @returns The file content. + */ +export function readJsonFile(filepath: string): any { + return jsonc.parse(fs.readFileSync(filepath).toString()); +} + +/** + * Read the file and parse the JSON. + * + * @param {string} filepath The path of the file. + * @param {any} data The data to write into the file. + * @returns The file content. + */ +export function writeJsonFile(filepath: string, data: any): any { + // Check if the "auto-generated-language-definitions" directory exists, + // and create it if it doesn't. + if (!fs.existsSync(this.autoGeneratedDir)) { + fs.mkdirSync(this.autoGeneratedDir); + } + + // Write the updated JSON back into the file and add tab indentation + // to make it easier to read. + fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); +} + +/** + * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, + * or some patterns are not working as expected. + * + * @param obj The object + * @param key The key to check in the object + * @returns {RegExp} The reconstructed regex pattern. + */ +export function reconstructRegex(obj: any, key: string) { + // If key has a "pattern" key, then it's an object... + if (Object.hasOwn(obj[key], "pattern")) { + return new RegExp(obj[key].pattern); + } + // Otherwise it's a string. + else { + return new RegExp(obj[key]); + } +} + +/** + * Convert a Map to an object with it's inner Map's keys and values reversed/switched. + * + * Code based on this StackOverflow answer https://stackoverflow.com/a/45728850/2358222 + * + * @param {Map>} m The Map to convert to an object. + * @returns {object} The converted object. + * + * @example + * reverseMapping( + * Map { + * "supportedLanguages" => Map { + * "apacheconf" => "#", + * "c" => "//", + * "clojure" => ";", + * "coffeescript" => "#", + * "cpp" => "//", + * … + * } + * } + * ); + * + * // Converts to: + * + * { + * "supportedLanguages" => { + * "#": [ + * "apacheconf", + * "coffeescript", + * ... + * ], + * "//": [ + * "c", + * "cpp", + * ... + * ], + * ";": [ + * "clojure", + * ... + * ] + * } + * } + */ +export function convertMapToReversedObject(m: Map>): object { + const result: any = {}; + + // Convert a nested key:value Map from inside another Map into an key:array object, + // while reversing/switching the keys and values. The Map's values are now the keys of + // the object and the Map's keys are now added as the values of the array. The reversed + // object is added to the key of the outerMap. + + // Loop through the outer Map... + for (const [key, innerMap] of m.entries()) { + // Convert the inner Map to an object + const o = Object.fromEntries(innerMap); + + // Reverse the inner object mapping. + // + // Loop through the object (o) keys, assigns a new object (r) with the value of the + // object key (k) as the new key (eg. "//") and the new value is an array of all + // the original object keys (o[k]) (eg. "php"). + // If the key (o[k]) already exists in the new object (r), then just add the + // original key to the array, otherwise start a new array ([]) with the original + // key as value ( (r[o[k]] || []).concat(k) ). + // Add this new reversed object to the result object with the outer map key + // as the key. + result[key] = Object.keys(o).reduce((r, k) => Object.assign(r, {[o[k]]: (r[o[k]] || []).concat(k)}), {}); + } + return result; +} From e6214cd39763d33c185a761566f7b693159ebc57 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 02:52:40 +0100 Subject: [PATCH 15/48] refactor: "directory exists" code in `writeJsonFile` into a new function - Added a new `ensureDirExists` utility function to create specified directory if it doesn't exist. - Moved the "directory exists" code in the `writeJsonFile` util function to the new function, but making it for generic use (removed references to the auto-generated directory). - Added the new function call into the `Configuration::writeCommentLanguageDefinitionsToJsonFile` method to ensure the auto generated dir exists. --- src/configuration.ts | 3 +++ src/utils.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 60ca232..cef0a9c 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -662,6 +662,9 @@ export class Configuration { * either multi-line-languages.json, or single-line-languages.json. */ private writeCommentLanguageDefinitionsToJsonFile() { + // Ensure the auto-generated directory exists. + utils.ensureDirExists(this.autoGeneratedDir); + // Write the into the single-line-languages.json file. utils.writeJsonFile(this.singleLineLangDefinitionFilePath, utils.convertMapToReversedObject(this.singleLineBlocksMap)); // Write the into the multi-line-languages.json file. diff --git a/src/utils.ts b/src/utils.ts index 2b124d9..b19bf57 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,17 +20,22 @@ export function readJsonFile(filepath: string): any { * @returns The file content. */ export function writeJsonFile(filepath: string, data: any): any { - // Check if the "auto-generated-language-definitions" directory exists, - // and create it if it doesn't. - if (!fs.existsSync(this.autoGeneratedDir)) { - fs.mkdirSync(this.autoGeneratedDir); - } - // Write the updated JSON back into the file and add tab indentation // to make it easier to read. fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); } +/** + * Ensure that the directory exists. If it doesn't exist, create it. + * + * @param {string} dir The directory path to ensure exists. + */ +export function ensureDirExists(dir: string) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } +} + /** * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, * or some patterns are not working as expected. From e5063bc2e671163e5957aec7ec8e6a218bb9638b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 06:22:50 +0100 Subject: [PATCH 16/48] refactor: replace `mergeConfigProperty` with `mergeArraysBy` utils function - Refactored `mergeConfigProperty` into a new generic `mergeArraysBy` utility function. - Removed `mergeConfigProperty` method from `Configuration` class. - Added new `mergeArraysBy` utility function in `utils` file, and updated the code to be more generic with better typings. - Replaced all references to the old method in `Configuration` with the new utils function. --- src/configuration.ts | 48 +++++--------------------------------------- src/utils.ts | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index cef0a9c..47b9a23 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -700,14 +700,10 @@ export class Configuration { let langConfig = {...internalLangConfig}; if (multiLine) { - langConfig.autoClosingPairs = this.mergeConfigProperty( - defaultMultiLineConfig.autoClosingPairs, - internalLangConfig?.autoClosingPairs, - "open" - ); + langConfig.autoClosingPairs = utils.mergeArraysBy(defaultMultiLineConfig.autoClosingPairs, internalLangConfig?.autoClosingPairs, "open"); // Add the multi-line onEnter rules to the langConfig. - langConfig.onEnterRules = this.mergeConfigProperty(Rules.multilineEnterRules, internalLangConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.multilineEnterRules, internalLangConfig?.onEnterRules, "beforeText"); // Only assign the default config comments if it doesn't already exist. // (nullish assignment operator ??=) @@ -736,15 +732,15 @@ export class Configuration { if (isOnEnter && singleLineStyle) { // //-style comments if (singleLineStyle === "//") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.slashEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.slashEnterRules, langConfig?.onEnterRules, "beforeText"); } // #-style comments else if (singleLineStyle === "#") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.hashEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.hashEnterRules, langConfig?.onEnterRules, "beforeText"); } // ;-style comments else if (singleLineStyle === ";") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.semicolonEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.semicolonEnterRules, langConfig?.onEnterRules, "beforeText"); } } // If isOnEnter is false AND singleLineStyle isn't false, i.e. a string. @@ -854,40 +850,6 @@ export class Configuration { return vscode.languages.setLanguageConfiguration(langId, langConfig); } - /** - * Merges two configuration properties arrays, removing any duplicates based on a - * specified property. - * - * @param {any[]} defaultConfigProperty The default configuration property array of objects. - * @param {any[]} internalConfigProperty The internal configuration property array of objects. - * @param {string} objectKey The key within the array item object to check against for preventing duplicates - * @returns {any[]} The merged configuration property array without duplicates. - */ - private mergeConfigProperty(defaultConfigProperty: any[], internalConfigProperty: any[], objectKey: string) { - // Define an empty array if the internalConfigProperty is undefined. - internalConfigProperty ??= []; - - // Copy to avoid side effects. - const merged = [...defaultConfigProperty]; - - /** - * Merge the arrays and remove any duplicates. - */ - - // Loop over the internalConfigProperty array... - internalConfigProperty.forEach((item) => - // Test all items in the merged array, and if the item's - // key is not already present in one of the merged array's objects then add the item - // to the merged array. - // - // Code based on "2023 update" portion of this StackOverflow answer: - // https://stackoverflow.com/a/1584377/2358222 - merged.some((mergedItem) => item[objectKey] === mergedItem[objectKey]) ? null : merged.push(item) - ); - - return merged; - } - /** * The keyboard binding event handler for the single-line blocks on shift+enter. * diff --git a/src/utils.ts b/src/utils.ts index b19bf57..ccbe6b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -125,3 +125,46 @@ export function convertMapToReversedObject(m: Map>): } return result; } + +/** + * Merges two arrays of objects, removing duplicates based on a specified property. + * + * Code based on "2023 update" portion of this StackOverflow answer: + * https://stackoverflow.com/a/1584377/2358222 + * + * @param {T[]} primaryArray The primary array of objects (takes precedence). + * @param {T[]} secondaryArray The secondary array of objects to merge in. + * @param {keyof T} key The property key to check for duplicates. + * + * @returns {T[]} The merged array without duplicates + * + * @example + * const users1 = [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]; + * const users2 = [{id: 2, name: 'Jane'}, {id: 3, name: 'Jane Doe'}]; + * const merged = mergeArraysBy(users1, users2, 'name'); + * // Result: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}, {id: 3, name: 'Jane Doe'}] + */ +export function mergeArraysBy(primaryArray: T[], secondaryArray: T[], key: keyof T): T[] { + // Handle undefined/null arrays + const primary = primaryArray || []; + const secondary = secondaryArray || []; + + // Start with primary array (avoids side effects) + const merged = [...primary]; + + // Add items from secondary array that don't exist in primary, + // removing any duplicates. + secondary.forEach((item) => { + // Test all items in the merged array to check if the value of the key + // already exists in the merged array. + const exists: boolean = merged.some((existingItem) => item[key] === existingItem[key]); + + // If the value of the key does not exist in the merged array, + // then add the item, which prevents duplicates. + if (!exists) { + merged.push(item); + } + }); + + return merged; +} From b0a40aa726510c2630e91ec995425ed114c72127 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 08:09:45 +0100 Subject: [PATCH 17/48] refactor: retrieval of extension data into a new `ExtensionData` class. Changed: - Moved `getExtensionPackageJsonData`, `setExtensionData`, `getExtensionData` methods into a new `ExtensionData` class for simplicity and better organisation. - Simplified the `getExtensionPackageJsonData` method to get all package.json data not just a select few. By using the new type `IPackageJson`, we can properly type the data as package.json instead of a generic `object` or `any`. - Changed the `name` variable to `namespace`, simplied it's code and added it to the `contributes.configuration` object of the packageJson data, since it's the namespace of the settings - Changed the `name` variable to `namespace`, simplied it's code and added it to the `contributes.configuration` object of the packageJson data, since it's the namespace of the configuration settings. - Split `setExtensionData` into 2 methods: `setExtensionData` and `createExtensionData`; and simplified the code. - `createExtensionData` to create the extension data object, defining the keys and values for the Map. The keys as defined here will also be used for type inference for VScode intellisense in the new `get` method. - `setExtensionData` to set the data as defined in `createExtensionData` method into the `extensionData` Map. - Changed all references to the old `getExtensionData` method to use the `get` method of the new `ExtensionData` class. - Renamed the `Configuration` property `extensionDetails` to `extensionData`; and instantiate the `ExtensionData` class as it's value. Added: - Added new `get` method to get the extension's data by a specified key. VScode intellisense auto-suggests the keys by using type inference from the keys set in `createExtensionData` method. This is important so remembering or looking up the keys isn't a pain. - Added `getAll` method to get all extension data as readonly so it can only be logged in the Output Channel. - Added a new dependency `package-json-type` to allow package.json typings as `IPackageJson`. --- package.json | 3 +- src/configuration.ts | 107 +++++++---------------------------------- src/extension.ts | 7 ++- src/extensionData.ts | 110 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 src/extensionData.ts diff --git a/package.json b/package.json index 5aaf61d..401dbbc 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ }, "dependencies": { "is-wsl": "^3.1.0", - "jsonc-parser": "^3.3.1" + "jsonc-parser": "^3.3.1", + "package-json-type": "^1.0.3" } } diff --git a/src/configuration.ts b/src/configuration.ts index 47b9a23..159273e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -9,6 +9,7 @@ import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; import * as utils from "./utils"; +import {ExtensionData} from "./extensionData"; export class Configuration { /************** @@ -23,9 +24,10 @@ export class Configuration { private logger: Logger; /** - * This extension details in the form of a key:value Map object, for ease of use. + * This extension data class instance. + * @type {ExtensionData} */ - private extensionDetails = new Map(); + private extensionData: ExtensionData = new ExtensionData(); /** * A key:value Map object of language IDs and their config file paths. @@ -66,10 +68,8 @@ export class Configuration { public constructor(logger: Logger) { this.logger = logger; - this.setExtensionData(); - // Always output extension information to channel on activate. - this.logger.debug(`Extension details:`, this.extensionDetails); + this.logger.debug(`Extension details:`, this.extensionData.getAll()); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -198,86 +198,13 @@ export class Configuration { } } - /** - * Get the names, id, and version of this extension from package.json. - * - * @returns {object} An object containing the extension id, name, display name, and version. - */ - private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { - const packageJSON = utils.readJsonFile(__dirname + "/../../package.json"); - - const displayName: string = packageJSON.displayName; - const fullname: string = packageJSON.name; - const id: string = `${packageJSON.publisher}.${fullname}`; - const version: string = packageJSON.version; - - let nameParts = fullname.split("-"); - nameParts[0] = "auto"; - const name = nameParts.join("-"); - - return {id: id, name: name, displayName: displayName, version: version}; - } - - /** - * Set the extension data into the extensionDetails Map. - */ - private setExtensionData() { - const extensionPackageJsonData = this.getExtensionPackageJsonData(); - - const id = extensionPackageJsonData.id; - const name = extensionPackageJsonData.name; - const displayName = extensionPackageJsonData.displayName; - const version = extensionPackageJsonData.version; - - // The path to the user extensions. - const userExtensionsPath = isWsl - ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); - - // The path to the built-in extensions. - // This env variable changes when on WSL to it's WSL-built-in extensions path. - const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); - - this.extensionDetails.set("id", id); - this.extensionDetails.set("name", name); - this.extensionDetails.set("displayName", displayName); - this.extensionDetails.set("version", version); - this.extensionDetails.set("userExtensionsPath", userExtensionsPath); - this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); - - if (isWsl) { - // Get the root path to VS Code from the env variable, and use it to get - // the Windows built-in extensions. - const windowsBuiltInExtensionsPathFromWsl = path.join(process.env.VSCODE_CWD, "resources/app/extensions"); - - // Get the Windows user extensions path from env variable. - const windowsUserExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); - - this.extensionDetails.set("WindowsUserExtensionsPathFromWsl", windowsUserExtensionsPathFromWsl); - this.extensionDetails.set("WindowsBuiltInExtensionsPathFromWsl", windowsBuiltInExtensionsPathFromWsl); - } - } - - /** - * Get the extension's details. - * - * @param {string} key The key of the specific extension detail to get. - * - * @returns {any} Returns a value of a specific key. - */ - public getExtensionData(key: string): any { - if (this.extensionDetails.has(key)) { - return this.extensionDetails.get(key); - } - } - /** * Get all the extension's configuration settings. * * @returns {vscode.WorkspaceConfiguration} */ public getConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration(this.getExtensionData("name"), null); + return vscode.workspace.getConfiguration(this.extensionData.get("name"), null); } /** @@ -368,15 +295,15 @@ export class Configuration { // If running in WSL... if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); // Read the paths and create arrays of the extensions. - const builtInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); - const userExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); + const windowsBuiltInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); + const windowsUserExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); // Combine the built-in and user extensions into the extensions array. - extensions.push(...builtInExtensions, ...userExtensions); + extensions.push(...windowsBuiltInExtensions, ...windowsUserExtensions); } // Add all installed extensions (including built-in ones) into the extensions array. @@ -916,7 +843,7 @@ export class Configuration { */ private handleChangeBladeMultiLineBlock(textEditor: vscode.TextEditor) { let langId = textEditor.document.languageId; - const extensionName = this.getExtensionData("name"); + const extensionName = this.extensionData.get("name"); // Only carry out function if languageId is blade. if (langId === "blade" && !this.isLangIdDisabled(langId)) { @@ -955,25 +882,25 @@ export class Configuration { private logDebugInfo() { // The path to the built-in extensions. The env variable changes when on WSL. // So we can use it for both Windows and WSL. - const builtInExtensionsPath = this.getExtensionData("builtInExtensionsPath"); + const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); let extensionsPaths = {}; if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); extensionsPaths = { "Windows-installed Built-in Extensions Path": windowsBuiltInExtensionsPath, "Windows-installed User Extensions Path": windowsUserExtensionsPath, "WSL-installed Built-in Extensions Path": builtInExtensionsPath, - "WSL-installed User Extensions Path": this.getExtensionData("userExtensionsPath"), + "WSL-installed User Extensions Path": this.extensionData.get("userExtensionsPath"), }; } else { extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": this.getExtensionData("userExtensionsPath"), + "User Extensions Path": this.extensionData.get("userExtensionsPath"), }; } diff --git a/src/extension.ts b/src/extension.ts index 20eefd1..0a771856 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,8 +4,10 @@ import * as vscode from "vscode"; import {Configuration} from "./configuration"; import {Logger} from "./logger"; +import {ExtensionData} from "./extensionData"; const logger = new Logger(); +const extensionData = new ExtensionData(); logger.setupOutputChannel(); let configuration = new Configuration(logger); @@ -17,8 +19,9 @@ export function activate(context: vscode.ExtensionContext) { disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); - const extensionName = configuration.getExtensionData("name"); - const extensionDisplayName = configuration.getExtensionData("displayName"); + const extensionName = extensionData.get("name"); + + const extensionDisplayName = extensionData.get("displayName"); let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); diff --git a/src/extensionData.ts b/src/extensionData.ts new file mode 100644 index 0000000..1f63bd3 --- /dev/null +++ b/src/extensionData.ts @@ -0,0 +1,110 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import isWsl from "is-wsl"; +import {IPackageJson} from "package-json-type"; + +import {readJsonFile} from "./utils"; + +export class ExtensionData { + /** + * This extension details in the form of a key:value Map object. + * + * @type {Map} + */ + private extensionData = new Map(); + + /** + * The package.json data for this extension. + * + * @type {IPackageJson} + */ + private packageJsonData: IPackageJson; + + public constructor() { + this.packageJsonData = this.getExtensionPackageJsonData(); + this.setExtensionData(); + } + + /** + * Get the names, id, and version of this extension from package.json. + * + * @returns {IPackageJson} The package.json data for this extension, plus the new `id`, and `contributes.configuration.namespace` keys. + */ + private getExtensionPackageJsonData(): IPackageJson { + const packageJSON: IPackageJson = readJsonFile(__dirname + "/../../package.json"); + + // Set the id (publisher.name) into the packageJSON object as a new `id` key. + packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; + + // The configuration settings namespace is a shortened version of the extension name. + // We just need to replace "automatic" with "auto" in the name. + const settingsNamespace: string = packageJSON.name.replace("automatic", "auto"); + // Set the namespace to the packageJSON `configuration` object as a new `namespace` key. + packageJSON.contributes.configuration.namespace = settingsNamespace; + + return packageJSON; + } + + /** + * Set the extension data into the extensionData Map. + */ + private setExtensionData() { + // Set all entries in the extensionData Map. + Object.entries(this.createExtensionData()).forEach(([key, value]) => { + this.extensionData.set(key, value); + }); + } + + /** + * Create the extension data object for the extensionData Map. + * It also helps for type inference intellisense in the get method. + * + * @returns The extension data object with keys and values. + */ + private createExtensionData() { + // The path to the user extensions. + const userExtensionsPath = isWsl + ? path.join(vscode.env.appRoot, "../../", "extensions") + : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + + // Set the keys and values for the Map. + // The keys will also be used for type inference in VSCode intellisense. + return { + id: this.packageJsonData.id, + name: this.packageJsonData.contributes.configuration.namespace, + displayName: this.packageJsonData.displayName, + version: this.packageJsonData.version, + userExtensionsPath: userExtensionsPath, + // The path to the built-in extensions. + // This env variable changes when on WSL to it's WSL-built-in extensions path. + builtInExtensionsPath: path.join(vscode.env.appRoot, "extensions"), + + // Only set these if running in WSL. + ...(isWsl && { + WindowsUserExtensionsPathFromWsl: path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!), + WindowsBuiltInExtensionsPathFromWsl: path.join(process.env.VSCODE_CWD!, "resources/app/extensions"), + }), + } as const; + } + + /** + * Get the extension's data by a specified key. + * + * @param {K} key The key of the extension detail to get. + * + * @returns {ReturnType[K] | undefined} The value of the extension detail, or undefined if the key does not exist. + */ + public get>(key: K): ReturnType[K] | undefined { + return this.extensionData.get(key) as ReturnType[K] | undefined; + } + + /** + * Get all extension data. + * + * @returns {ReadonlyMap} A read-only Map containing all extension details. + */ + public getAll(): ReadonlyMap { + return this.extensionData; + } +} From f08044bccbadea0721535926e89c0861e21a4bc9 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 08:44:11 +0100 Subject: [PATCH 18/48] fix: `vscode.extensions.all` API doesn't include any disabled extensions VS Code's `extensions.all` API only gets enabled extensions and doesn't include disabled ones, which could prevent language configs being found. To fix, we completely remove getting the extensions from the vscode API, and instead get the extensions directly from the directories. - Added the ability to get the built-in and user extensions directly from the directory on non-WSL systems (eg. Windows), but using the same way it gets extensions on WSL using the extension data path keys and `readExtensionsFromDirectory` method. - Removed `vscode.extensions.all` API call from `findAllLanguageConfigFilePaths` method. --- src/configuration.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 159273e..995f1f6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -306,9 +306,16 @@ export class Configuration { extensions.push(...windowsBuiltInExtensions, ...windowsUserExtensions); } + const userExtensionsPath = this.extensionData.get("userExtensionsPath"); + const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); + + // Read the paths and create arrays of the extensions. + const userExtensions = this.readExtensionsFromDirectory(userExtensionsPath); + const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPath); + // Add all installed extensions (including built-in ones) into the extensions array. // If running WSL, these will be the WSL-installed extensions. - extensions.push(...vscode.extensions.all); + extensions.push(...builtInExtensions, ...userExtensions); // Loop through all installed extensions, including built-in extensions for (let extension of extensions) { From e2f37015af49e138f6c380dabd7e002fc15185b7 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 23:04:18 +0100 Subject: [PATCH 19/48] fix: TS compile error: Cannot find name 'id'. https://github.com/yCodeTech/auto-comment-blocks/actions/runs/16339281667/job/46157703386?pr=12 This error occurs because testing used a hard-coded path to simulate what the `vscode.extensions.getExtension().extensionPath` API would get. Since commit b0a40aa changed the way we get/set the package.json data, this was just an oversight. Since we're already using the path of the extension to get the package.json in `getExtensionPackageJsonData`, we can just leverage this, and properly integrate the path. Fixed by: - Adding the extension path as a new `extensionPath` key in the `packageJSON` data object. - Completely removing the VS Code's `.extensions.getExtension()` API call in `createExtensionData` method and changing it to use the `extensionPath` key in the `packageJsonData` property. --- src/extensionData.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 1f63bd3..473513a 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -28,13 +28,16 @@ export class ExtensionData { /** * Get the names, id, and version of this extension from package.json. * - * @returns {IPackageJson} The package.json data for this extension, plus the new `id`, and `contributes.configuration.namespace` keys. + * @returns {IPackageJson} The package.json data for this extension, with extra custom keys. */ private getExtensionPackageJsonData(): IPackageJson { - const packageJSON: IPackageJson = readJsonFile(__dirname + "/../../package.json"); + const extensionPath = path.join(__dirname, "../../"); + + const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json")); // Set the id (publisher.name) into the packageJSON object as a new `id` key. packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; + packageJSON.extensionPath = extensionPath; // The configuration settings namespace is a shortened version of the extension name. // We just need to replace "automatic" with "auto" in the name. @@ -65,8 +68,7 @@ export class ExtensionData { // The path to the user extensions. const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); - + : path.join(this.packageJsonData.extensionPath, "../"); // Set the keys and values for the Map. // The keys will also be used for type inference in VSCode intellisense. From 1a56d6b206cf38af645c0940e596b3003607b98f Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 3 Jul 2025 04:29:08 +0100 Subject: [PATCH 20/48] refactor: extract debug logging from `constructor` into new method - Added the new `logDebugInfo` method to log all debugging information to the Output channel. - Extracted all the debug logging from the `constructor` into it's own method to keep the constructor clean. - Changed the format of the data logged for the supported languages. Instead of logging the `Map`s, we now utilise the auto-generated language definitions files. This is because the single-line languages are reformatted in a human-readable way to output to the file, so it would be better reading this formatted file than the JS `Map`. --- src/configuration.ts | 47 +++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 9f4ab51..cdeeaad 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -65,21 +65,6 @@ export class Configuration { const extensionVersion = vscode.extensions.getExtension(extensionId)?.packageJSON.version; this.logger.info(`Extension: ${extensionId} (${extensionVersion})`); - const env = { - "OS": process.platform, - "Platform": process.platform, - "VS Code Version": vscode.version, - "VS Code Root Path": vscode.env.appRoot, - "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, - "VS Code Host": vscode.env.appHost, - "VS Code Remote Name": vscode.env.remoteName || "local", - "Other System Env Variables": process.env, - }; - this.logger.debug("Environment:", env); - - // Log the extension's user configuration settings. - this.logger.debug("Configuration settings:", this.getConfiguration()); - this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -87,11 +72,7 @@ export class Configuration { this.setSingleLineCommentLanguageDefinitions(); this.writeCommentLanguageDefinitionsToJsonFile(); - // Log the objects for debugging purposes. - this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); - this.logger.debug("The language configs found are:", this.languageConfigs); - this.logger.debug("The supported languages for multi-line blocks:", this.multiLineBlocksMap); - this.logger.debug("The supported languages single-line blocks:", this.singleLineBlocksMap); + this.logDebugInfo(); } /** @@ -1001,4 +982,30 @@ export class Configuration { this.setBladeComments(false); } } + + /** + * Logs the environment, configuration settings, and language configs for debugging purposes. + */ + private logDebugInfo() { + const env = { + "OS": process.platform, + "Platform": process.platform, + "VS Code Version": vscode.version, + "VS Code Root Path": vscode.env.appRoot, + "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, + "VS Code Host": vscode.env.appHost, + "VS Code Remote Name": vscode.env.remoteName || "local", + "Other System Env Variables": process.env, + }; + this.logger.debug("Environment:", env); + + // Log the extension's user configuration settings. + this.logger.debug("Configuration settings:", this.getConfiguration()); + + // Log the objects for debugging purposes. + this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); + this.logger.debug("The language configs found are:", this.languageConfigs); + this.logger.debug("The supported languages for multi-line blocks:", this.readJsonFile(this.multiLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for single-line blocks:", this.readJsonFile(this.singleLineLangDefinitionFilePath)); + } } From 00952ac2001577242d217b4f52a9ceebb6273d71 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 9 Jul 2025 19:02:48 +0100 Subject: [PATCH 21/48] build: add new dependency to detect if WSL is being used. --- package.json | 1 + src/configuration.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/package.json b/package.json index f3c75be..70511f3 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "typescript": "^5.7" }, "dependencies": { + "is-wsl": "^3.1.0", "jsonc-parser": "^3.3.1" } } diff --git a/src/configuration.ts b/src/configuration.ts index cdeeaad..6322ffb 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode"; import * as fs from "node:fs"; import * as jsonc from "jsonc-parser"; import * as path from "path"; +import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; From b313aa8aba3eac44b4120c900245a329bcc601ba Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 02:01:59 +0100 Subject: [PATCH 22/48] feat: add the ability to get all Windows extensions when running on WSL. This is a work around for the fact that the `vscode.extensions.all` API can't find any built-in extensions on the Windows-side when running in WSL. It only gets the WSL-installed extensions, which caused the extension not to work in yCodeTech/auto-comment-blocks#6 There is an open issue and a proposed API change in the sourcecode since 2022, but hasn't been released as a public API. See https://github.com/microsoft/vscode/issues/145307 for more info. Added: - Added new `getExtensionsPathsFromWslEnv` method to retrieve the Windows built-in and user extensions paths from the environment variables when running in WSL. - Added new `readExtensionsFromDirectory` method to read the extensions paths and retrieve each extension data. Changed: - Changed `findAllLanguageConfigFilePaths` method to check if the extension is running in WSL. If it `isWsl`, then it calls the new `getExtensionsPathsFromWslEnv` method to get the paths, and uses the new `readExtensionsFromDirectory` to obtain the extensions data. We construct a new `extensions` array with all the windows-installed built-in and user extensions and also the WSL-installed extensions via `vscode.extensions.all`. (The extension will still work on Windows without WSL due to it using `vscode.extensions.all` outside of the `isWsl` check.) - Changed the `for...of` loop to iterate through the new `extensions` array. --- src/configuration.ts | 90 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 6322ffb..0791807 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -304,8 +304,27 @@ export class Configuration { * (built-in and 3rd party). */ private findAllLanguageConfigFilePaths() { + const extensions: any[] = []; + + // If running in WSL... + if (isWsl) { + // Get the Windows built-in and user extensions paths from WSL environment variables. + const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + + // Read the paths and create arrays of the extensions. + const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPathFromWsl); + const userExtensions = this.readExtensionsFromDirectory(userExtensionsPathFromWsl); + + // Combine the built-in and user extensions into the extensions array. + extensions.push(...builtInExtensions, ...userExtensions); + } + + // Add all installed extensions (including built-in ones) into the extensions array. + // If running WSL, these will be the WSL-installed extensions. + extensions.push(...vscode.extensions.all); + // Loop through all installed extensions, including built-in extensions - for (let extension of vscode.extensions.all) { + for (let extension of extensions) { const packageJSON = extension.packageJSON; // If an extension package.json has "contributes" key, @@ -425,6 +444,41 @@ export class Configuration { fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); } + /** + * Read the directory in the given path and return an array of objects with the data of + * all extensions found in the directory. + * + * @param {string} extensionsPath The path where extensions are stored. + * + * @returns {Array<{ id: string; extensionPath: string; packageJSON: any }>} + */ + private readExtensionsFromDirectory(extensionsPath: string): Array<{id: string; extensionPath: string; packageJSON: any}> { + // Create an array to hold the found extensions. + const foundExtensions: Array<{id: string; extensionPath: string; packageJSON: any}> = []; + + fs.readdirSync(extensionsPath).forEach((extensionName) => { + const extensionPath = path.join(extensionsPath, extensionName); + + // If the extensionName is a directory... + if (fs.statSync(extensionPath).isDirectory()) { + // Get the package.json file path. + const packageJSONPath = path.join(extensionPath, "package.json"); + + // If the package.json file exists... + if (fs.existsSync(packageJSONPath)) { + const packageJSON = this.readJsonFile(packageJSONPath); + + const id = `${packageJSON.publisher}.${packageJSON.name}`; + + // Push the extension data object into the array. + foundExtensions.push({id, extensionPath, packageJSON}); + } + } + }); + + return foundExtensions; + } + /** * Get the multi-line languages from the Map. * @@ -984,6 +1038,40 @@ export class Configuration { } } + /** + * Gets the user and built-in extensions paths on Windows from WSL environment variables. + * + * @returns {Object} An object containing the user and built-in extensions paths. + */ + private getExtensionsPathsFromWslEnv() { + const extensionNames = this.getExtensionNames(); + + /** Built-in Extensions */ + + // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. + // The variable will be a path like: + // "/mnt/c/Users/USERNAME/AppData/Local/Programs/Microsoft VS Code". + const vscodePathFromWsl = process.env.VSCODE_CWD; + // Append "resources/app/extensions" to the base path to form the path to the + // built-in extensions. + const builtInExtensionsPathFromWsl = path.join(vscodePathFromWsl, "resources/app/extensions"); + + /** User Extensions */ + + // Get the user extensions path from VSCODE_WSL_EXT_LOCATION env variable. + // If it's not set, then use an empty string. + // The variable will be a path like: + // "/mnt/c/Users/USERNAME/.vscode/extensions/ms-vscode-remote.remote-wsl-0.99.0". + // So we just need to return the directory name like: + // "/mnt/c/Users/USERNAME/.vscode/extensions/" + const userExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); + + return { + builtInExtensionsPathFromWsl, + userExtensionsPathFromWsl, + }; + } + /** * Logs the environment, configuration settings, and language configs for debugging purposes. */ From 4e790c829dc482f6f8b55c5d20d517eadf4c546d Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 02:10:45 +0100 Subject: [PATCH 23/48] refactor: `logDebugInfo` method to log different stuff when on WSL. Added: - Added an `isWsl` check, and uses the new `getExtensionsPathsFromWslEnv` method to log the Windows extensions paths from WSL. Changed: - Refactored all "VS Code" items into a new object so that we're not repeating "VS Code" multiple times. This makes for a better reading experience. --- src/configuration.ts | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 0791807..3118733 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1076,14 +1076,38 @@ export class Configuration { * Logs the environment, configuration settings, and language configs for debugging purposes. */ private logDebugInfo() { + // The path to the built-in extensions. The env variable changes when on WSL. + // So we can use it for both Windows and WSL. + const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); + + let extensionsPaths = {}; + + if (isWsl) { + // Get the Windows user and built-in extensions paths on Windows from WSL environment variables. + const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + + extensionsPaths = { + "Windows-installed Built-in Extensions Path": builtInExtensionsPathFromWsl, + "Windows-installed User Extensions Path": userExtensionsPathFromWsl, + "WSL-installed Built-in Extensions Path": builtInExtensionsPath, + "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), + }; + } else { + extensionsPaths = { + "Built-in Extensions Path": builtInExtensionsPath, + "User Extensions Path": path.join(process.env.USERPROFILE, ".vscode", "extensions"), + }; + } + const env = { "OS": process.platform, "Platform": process.platform, - "VS Code Version": vscode.version, - "VS Code Root Path": vscode.env.appRoot, - "VS Code Built-in Extensions Path": `${vscode.env.appRoot}\\extensions`, - "VS Code Host": vscode.env.appHost, - "VS Code Remote Name": vscode.env.remoteName || "local", + "VS Code Details": { + "Version": vscode.version, + "Remote Name": vscode.env.remoteName || "local", + "Host": vscode.env.appHost, + ...extensionsPaths, + }, "Other System Env Variables": process.env, }; this.logger.debug("Environment:", env); From 338587e65be6d852053ceb4ab9a3634b86acd68f Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 03:01:57 +0100 Subject: [PATCH 24/48] fix: keybindings, docs, and paths to enable usage on macOS. --- README.md | 6 +++--- package.json | 5 +++-- src/configuration.ts | 5 ++++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 03e0a91..656cf7f 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Use `/*!` in all file types that support the normal `/*` comments to start a QDo #### Normal comment blocks -Using the normal comment block `/* */` either typing manually or the native VScode command "Toggle Block Comment" (`editor.action.blockComment`, native keybinding `shift + alt + a`), the block will have the same on enter functionality as described above. +Using the normal comment block `/* */` either typing manually or the native VScode command "Toggle Block Comment" (`editor.action.blockComment`, native keybinding shift + alt + a (macOS: shift + option + a)), the block will have the same on enter functionality as described above. ![block-comments](https://raw.githubusercontent.com/yCodeTech/auto-comment-blocks/master/img/block-comments.gif) @@ -156,9 +156,9 @@ Reload the extension after changing any settings. - `auto-comment-blocks.multiLineStyleBlocks`: Add language IDs here to enable multi-line comment blocks support for that language, allowing unsupported languages to have comment completion. The default is `['blade', 'html']`" -- `auto-comment-blocks.overrideDefaultLanguageMultiLineComments`: A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode "Toggle Block Comment" `editor.action.blockComment` command (native Keybinding `shift + alt + a`). eg. `{'php': '/*!'}` +- `auto-comment-blocks.overrideDefaultLanguageMultiLineComments`: A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode "Toggle Block Comment" `editor.action.blockComment` command (native Keybinding shift + alt + a (macOS: shift + option + a)). eg. `{'php': '/*!'}` -- `auto-comment-blocks.bladeOverrideComments`: When enabled, Blade-style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m`. If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used. +- `auto-comment-blocks.bladeOverrideComments`: When enabled, Blade-style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default ctrl + shift + m (macOS: cmd + shift + m). If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used. ## Known Issues diff --git a/package.json b/package.json index 70511f3..5aaf61d 100644 --- a/package.json +++ b/package.json @@ -71,13 +71,13 @@ "auto-comment-blocks.overrideDefaultLanguageMultiLineComments": { "type": "object", "default": {}, - "markdownDescription": "A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode `command editor.action.blockComment` (native Keybinding `shift + alt + a`). eg. `{'php': '/*!'}`" + "markdownDescription": "A key : value pairing of language IDs and the beginning portion of a multi-line comment style, to override the default comment style for the vscode `command editor.action.blockComment` (native Keybinding `shift + alt + a` (macOS: `shift + option + a`)). eg. `{'php': '/*!'}`" }, "auto-comment-blocks.bladeOverrideComments": { "scope": "resource", "type": "boolean", "default": false, - "markdownDescription": "When enabled, Blade style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m`. If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used." + "markdownDescription": "When enabled, Blade style block comments will be used in Blade contexts. Ie. `{{-- --}}` comments will be used instead of the HTML `` comments. Keybinding to enable/disable, default `ctrl + shift + m` (macOS: `cmd + shift + m`). If `blade` language ID is set in the disabledLanguages, then the HTML `` comments will be used." } } }, @@ -90,6 +90,7 @@ { "command": "auto-comment-blocks.changeBladeMultiLineBlock", "key": "ctrl+shift+m", + "mac": "cmd+shift+m", "when": "editorTextFocus" } ] diff --git a/src/configuration.ts b/src/configuration.ts index 3118733..679efe6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1093,9 +1093,12 @@ export class Configuration { "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), }; } else { + // Get the user home directory from the environment variable: + // `USERPROFILE` on Windows, `HOME` on macOS/Linux. + const userHome = process.env.USERPROFILE || process.env.HOME; extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": path.join(process.env.USERPROFILE, ".vscode", "extensions"), + "User Extensions Path": path.join(userHome, ".vscode", "extensions"), }; } From 423fc5899f1f2ee2b8ac6de43cc3bbec0e38e959 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 05:59:34 +0100 Subject: [PATCH 25/48] refactor: create a uniform way of obtaining this extension's details. Added: - Added new `extensionDetails` private property in `Configuration` as a Map object. - Added new `setExtensionData` method to set the data into the `extensionDetails` Map. Also added its method call into the `constructor`. - Added new `getExtensionData` public method to get the value of a specified key from the `extensionDetails` Map. Changed: - Refactored `getExtensionNames` to be more generic. This will be used in the new `setExtensionData` method to get and set the names, id and version into the `extensionDetails` Map. - Changed the name to `getExtensionPackageJsonData`. - Changed it's visibility to `private`. - Changed the reading of the package.json file to use the `readJsonFile` method as it does the exact same thing, and makes it DRYer. - Added the extension's version. - Changed all references to the old `getExtensionNames` method to use the new `getExtensionData` method with the specific key needed. Removed: - Removed the unused `extensionNames` variable and method call from the `getExtensionsPathsFromWslEnv` method. --- src/configuration.ts | 65 +++++++++++++++++++++++++++++++++++--------- src/extension.ts | 6 ++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 679efe6..b36b07d 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -22,6 +22,11 @@ export class Configuration { */ private logger: Logger; + /** + * This extension details in the form of a key:value Map object, for ease of use. + */ + private extensionDetails = new Map(); + /** * A key:value Map object of language IDs and their config file paths. */ @@ -61,10 +66,12 @@ export class Configuration { public constructor(logger: Logger) { this.logger = logger; + this.setExtensionData(); + // Always output extension information to channel on activate. - const extensionId = this.getExtensionNames().id; - const extensionVersion = vscode.extensions.getExtension(extensionId)?.packageJSON.version; - this.logger.info(`Extension: ${extensionId} (${extensionVersion})`); + const id = this.getExtensionData("id"); + const version = this.getExtensionData("version"); + this.logger.info(`Extension: ${id} (${version})`); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -194,22 +201,56 @@ export class Configuration { } /** - * Get the names and ids of this extension from package.json. + * Get the names, id, and version of this extension from package.json. * - * @returns {object} An object containing the extension id, name, and display name. + * @returns {object} An object containing the extension id, name, display name, and version. */ - public getExtensionNames(): {id: string; name: string; displayName: string} { - const packageJSON = JSON.parse(fs.readFileSync(__dirname + "/../../package.json").toString()); + private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { + const packageJSON = this.readJsonFile(__dirname + "/../../package.json"); const displayName: string = packageJSON.displayName; const fullname: string = packageJSON.name; const id: string = `${packageJSON.publisher}.${fullname}`; + const version: string = packageJSON.version; let nameParts = fullname.split("-"); nameParts[0] = "auto"; const name = nameParts.join("-"); - return {id: id, name: name, displayName: displayName}; + return {id: id, name: name, displayName: displayName, version: version}; + } + + /** + * Set the extension data into the extensionDetails Map. + */ + private setExtensionData() { + const extensionPackageJsonData = this.getExtensionPackageJsonData(); + + const id = extensionPackageJsonData.id; + const name = extensionPackageJsonData.name; + const displayName = extensionPackageJsonData.displayName; + const version = extensionPackageJsonData.version; + + const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + this.extensionDetails.set("id", id); + this.extensionDetails.set("name", name); + this.extensionDetails.set("displayName", displayName); + this.extensionDetails.set("version", version); + this.extensionDetails.set("userExtensionsPath", userExtensionsPath); + } + + /** + * Get the extension's details. + * + * @param {string} key The key of the specific extension detail to get. + * + * @returns {any} Returns a value of a specific key. + */ + public getExtensionData(key: string): any { + if (this.extensionDetails.has(key)) { + return this.extensionDetails.get(key); + } } /** @@ -218,7 +259,7 @@ export class Configuration { * @returns {vscode.WorkspaceConfiguration} */ public getConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration(this.getExtensionNames().name, null); + return vscode.workspace.getConfiguration(this.getExtensionData("name"), null); } /** @@ -1005,7 +1046,7 @@ export class Configuration { */ private handleChangeBladeMultiLineBlock(textEditor: vscode.TextEditor) { let langId = textEditor.document.languageId; - const extensionNames = this.getExtensionNames(); + const extensionName = this.getExtensionData("name"); // Only carry out function if languageId is blade. if (langId === "blade" && !this.isLangIdDisabled(langId)) { @@ -1029,7 +1070,7 @@ export class Configuration { // then output a message to the user. else if (langId == "blade" && this.isLangIdDisabled(langId)) { vscode.window.showInformationMessage( - `Blade is set as disabled in the "${extensionNames.name}.disabledLanguages" setting. The "${extensionNames.name}.bladeOverrideComments" setting will have no affect.`, + `Blade is set as disabled in the "${extensionName}.disabledLanguages" setting. The "${extensionName}.bladeOverrideComments" setting will have no affect.`, "OK" ); @@ -1044,8 +1085,6 @@ export class Configuration { * @returns {Object} An object containing the user and built-in extensions paths. */ private getExtensionsPathsFromWslEnv() { - const extensionNames = this.getExtensionNames(); - /** Built-in Extensions */ // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. diff --git a/src/extension.ts b/src/extension.ts index 87da677..20eefd1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -17,10 +17,8 @@ export function activate(context: vscode.ExtensionContext) { disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); - const extensionNames = configuration.getExtensionNames(); - - const extensionName = extensionNames.name; - const extensionDisplayName = extensionNames.displayName; + const extensionName = configuration.getExtensionData("name"); + const extensionDisplayName = configuration.getExtensionData("displayName"); let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); From 03c856c1475f95b3705da45ce1f2a9a9219c1957 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 06:16:23 +0100 Subject: [PATCH 26/48] fix: `logDebugInfo` user extensions path to use `getExtensionData`. Previously we manually created the user extensions path from the home directory env variables and concatenated strings on to it. This is only good if the default extensions directory is used. This will fail if the user has changed the directory location with the CLI `code --extensions-dir ` So to fix, we're now getting the user extensions path from where ever this extension is located. The code for this is defined in `setExtensionData` method as committed in 25fab48. - Changed the construction of the users extensions path in `logDebugInfo` to use the `getExtensionData` method to get the path. - Removed the unneeded home directory variables. --- src/configuration.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index b36b07d..615fbd5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1132,12 +1132,9 @@ export class Configuration { "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), }; } else { - // Get the user home directory from the environment variable: - // `USERPROFILE` on Windows, `HOME` on macOS/Linux. - const userHome = process.env.USERPROFILE || process.env.HOME; extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": path.join(userHome, ".vscode", "extensions"), + "User Extensions Path": this.getExtensionData("userExtensionsPath"), }; } From 5d76d3be88b67a9d18fc56dba69a499763fbb13d Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 10 Jul 2025 06:24:45 +0100 Subject: [PATCH 27/48] feat: set the built-in extensions path into `extensionDetails` - Moved the code to get the built-in extensions path from `logDebugInfo` in to the `setExtensionData` method. - Added the built-in extensions path to the `extensionDetails` Map in `setExtensionData` method. - Updated `logDebugInfo` method to get the `builtInExtensionsPath` key from the Map using `getExtensionData` method. --- src/configuration.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 615fbd5..2857df1 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -232,12 +232,14 @@ export class Configuration { const version = extensionPackageJsonData.version; const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); this.extensionDetails.set("id", id); this.extensionDetails.set("name", name); this.extensionDetails.set("displayName", displayName); this.extensionDetails.set("version", version); this.extensionDetails.set("userExtensionsPath", userExtensionsPath); + this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); } /** @@ -1117,7 +1119,7 @@ export class Configuration { private logDebugInfo() { // The path to the built-in extensions. The env variable changes when on WSL. // So we can use it for both Windows and WSL. - const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); + const builtInExtensionsPath = this.getExtensionData("builtInExtensionsPath"); let extensionsPaths = {}; From f7811d21814b5601f7201629aa132d3f31a864d0 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:01:08 +0100 Subject: [PATCH 28/48] change: extension logger info to log the `extensionDetails` Map instead. --- src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 2857df1..a36ffb4 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -71,7 +71,7 @@ export class Configuration { // Always output extension information to channel on activate. const id = this.getExtensionData("id"); const version = this.getExtensionData("version"); - this.logger.info(`Extension: ${id} (${version})`); + this.logger.debug(`Extension details:`, this.extensionDetails); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); From bf96ee33294f43ba3868297c0100c17ad28c3f5b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:20:16 +0100 Subject: [PATCH 29/48] feat: add WSL paths to `setExtensionData` method. Added: - Added the WSL built-in and user extensions paths code to the `setExtensionData` method and set them into the `extensionDetails` with new Map keys. - Moved the environment variable code to get the WSL-installed user extensions path from the `logDebugInfo` method, and added the `getExtensionData` method call to the log item instead. - Refactored the `getExtensionsPathsFromWslEnv` method into `setExtensionData` method, simplifying the code. Removed: - Removed the now unused `getExtensionsPathsFromWslEnv` method, and changed all references to use the `getExtensionData` with the appropriate key. --- src/configuration.ts | 72 ++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index a36ffb4..862512b 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -231,7 +231,13 @@ export class Configuration { const displayName = extensionPackageJsonData.displayName; const version = extensionPackageJsonData.version; - const userExtensionsPath = path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + // The path to the user extensions. + const userExtensionsPath = isWSL + ? path.join(vscode.env.appRoot, "../../", "extensions") + : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + // The path to the built-in extensions. + // This env variable changes when on WSL to it's WSL-built-in extensions path. const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); this.extensionDetails.set("id", id); @@ -240,6 +246,18 @@ export class Configuration { this.extensionDetails.set("version", version); this.extensionDetails.set("userExtensionsPath", userExtensionsPath); this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); + + if (isWsl) { + // Get the root path to VS Code from the env variable, and use it to get + // the Windows built-in extensions. + const windowsBuiltInExtensionsPathFromWsl = path.join(process.env.VSCODE_CWD, "resources/app/extensions"); + + // Get the Windows user extensions path from env variable. + const windowsUserExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); + + this.extensionDetails.set("WindowsUserExtensionsPathFromWsl", windowsUserExtensionsPathFromWsl); + this.extensionDetails.set("WindowsBuiltInExtensionsPathFromWsl", windowsBuiltInExtensionsPathFromWsl); + } } /** @@ -351,12 +369,13 @@ export class Configuration { // If running in WSL... if (isWsl) { - // Get the Windows built-in and user extensions paths from WSL environment variables. - const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + // Get the Windows user and built-in extensions paths. + const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); // Read the paths and create arrays of the extensions. - const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPathFromWsl); - const userExtensions = this.readExtensionsFromDirectory(userExtensionsPathFromWsl); + const builtInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); + const userExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); // Combine the built-in and user extensions into the extensions array. extensions.push(...builtInExtensions, ...userExtensions); @@ -1081,38 +1100,6 @@ export class Configuration { } } - /** - * Gets the user and built-in extensions paths on Windows from WSL environment variables. - * - * @returns {Object} An object containing the user and built-in extensions paths. - */ - private getExtensionsPathsFromWslEnv() { - /** Built-in Extensions */ - - // Get the path to the VS Code's exectutable from the VSCODE_CWD environment variable. - // The variable will be a path like: - // "/mnt/c/Users/USERNAME/AppData/Local/Programs/Microsoft VS Code". - const vscodePathFromWsl = process.env.VSCODE_CWD; - // Append "resources/app/extensions" to the base path to form the path to the - // built-in extensions. - const builtInExtensionsPathFromWsl = path.join(vscodePathFromWsl, "resources/app/extensions"); - - /** User Extensions */ - - // Get the user extensions path from VSCODE_WSL_EXT_LOCATION env variable. - // If it's not set, then use an empty string. - // The variable will be a path like: - // "/mnt/c/Users/USERNAME/.vscode/extensions/ms-vscode-remote.remote-wsl-0.99.0". - // So we just need to return the directory name like: - // "/mnt/c/Users/USERNAME/.vscode/extensions/" - const userExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); - - return { - builtInExtensionsPathFromWsl, - userExtensionsPathFromWsl, - }; - } - /** * Logs the environment, configuration settings, and language configs for debugging purposes. */ @@ -1124,14 +1111,15 @@ export class Configuration { let extensionsPaths = {}; if (isWsl) { - // Get the Windows user and built-in extensions paths on Windows from WSL environment variables. - const {builtInExtensionsPathFromWsl, userExtensionsPathFromWsl} = this.getExtensionsPathsFromWslEnv(); + // Get the Windows user and built-in extensions paths. + const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); extensionsPaths = { - "Windows-installed Built-in Extensions Path": builtInExtensionsPathFromWsl, - "Windows-installed User Extensions Path": userExtensionsPathFromWsl, + "Windows-installed Built-in Extensions Path": windowsBuiltInExtensionsPath, + "Windows-installed User Extensions Path": windowsUserExtensionsPath, "WSL-installed Built-in Extensions Path": builtInExtensionsPath, - "WSL-installed User Extensions Path": path.join(vscode.env.appRoot, "../../", "extensions"), + "WSL-installed User Extensions Path": this.getExtensionData("userExtensionsPath"), }; } else { extensionsPaths = { From 64f79625613f4eb6e2ec67cc75259667ff05e4b5 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 11 Jul 2025 03:22:39 +0100 Subject: [PATCH 30/48] fix: `readExtensionsFromDirectory` to skip directories starting with a dot. --- src/configuration.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/configuration.ts b/src/configuration.ts index 862512b..bf7f918 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -523,6 +523,11 @@ export class Configuration { // If the extensionName is a directory... if (fs.statSync(extensionPath).isDirectory()) { + // If the extensionName starts with a dot, skip it. + if (extensionName.startsWith(".")) { + return; + } + // Get the package.json file path. const packageJSONPath = path.join(extensionPath, "package.json"); From 99cac65410d89bda0add621e5f22a5c05a46463b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 12 Jul 2025 01:06:51 +0100 Subject: [PATCH 31/48] fix: TS compile error: Cannot find name 'isWSL'. Did you mean 'isWsl'? https://github.com/yCodeTech/auto-comment-blocks/actions/runs/16210470528 --- src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index bf7f918..de1cdf2 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -232,7 +232,7 @@ export class Configuration { const version = extensionPackageJsonData.version; // The path to the user extensions. - const userExtensionsPath = isWSL + const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); From 89db52383c7f43fc8c518725a8632074a833ebae Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 16 Jul 2025 02:25:13 +0100 Subject: [PATCH 32/48] remove: unused variables which the usages were removed in commit 140cba1 --- src/configuration.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index de1cdf2..112ad9f 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -69,8 +69,6 @@ export class Configuration { this.setExtensionData(); // Always output extension information to channel on activate. - const id = this.getExtensionData("id"); - const version = this.getExtensionData("version"); this.logger.debug(`Extension details:`, this.extensionDetails); this.findAllLanguageConfigFilePaths(); From 7aafe3713b3f9a4ed4693edc496beaa6fe015fe7 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 02:36:56 +0100 Subject: [PATCH 33/48] refactor: move utility functions to a new `utils` file. - Moved utility functions to a new `utils` file for better organisation and reusability between files. - `readJsonFile` - `writeJsonFile` - `reconstructRegex` - `convertMapToReversedObject` - Changed all references to these functions to use the new namespace `utils` import. --- src/configuration.ts | 159 ++++++------------------------------------- src/utils.ts | 122 +++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 139 deletions(-) create mode 100644 src/utils.ts diff --git a/src/configuration.ts b/src/configuration.ts index 112ad9f..60ca232 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -3,12 +3,12 @@ import * as vscode from "vscode"; import * as fs from "node:fs"; -import * as jsonc from "jsonc-parser"; import * as path from "path"; import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; +import * as utils from "./utils"; export class Configuration { /************** @@ -204,7 +204,7 @@ export class Configuration { * @returns {object} An object containing the extension id, name, display name, and version. */ private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { - const packageJSON = this.readJsonFile(__dirname + "/../../package.json"); + const packageJSON = utils.readJsonFile(__dirname + "/../../package.json"); const displayName: string = packageJSON.displayName; const fullname: string = packageJSON.name; @@ -354,7 +354,7 @@ export class Configuration { * @returns {string[]} */ private getLanguagesToSkip(): string[] { - const json = this.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`); + const json = utils.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`); return json.languages; } @@ -417,7 +417,7 @@ export class Configuration { */ private setLanguageConfigDefinitions() { this.languageConfigFilePaths.forEach((filepath, langId) => { - const config = this.readJsonFile(filepath); + const config = utils.readJsonFile(filepath); // If the config JSON has more than 0 keys (ie. not empty) if (Object.keys(config).length > 0) { @@ -475,35 +475,6 @@ export class Configuration { } } - /** - * Read the file and parse the JSON. - * - * @param {string} filepath The path of the file. - * @returns The file content. - */ - private readJsonFile(filepath: string): any { - return jsonc.parse(fs.readFileSync(filepath).toString()); - } - - /** - * Read the file and parse the JSON. - * - * @param {string} filepath The path of the file. - * @param {any} data The data to write into the file. - * @returns The file content. - */ - private writeJsonFile(filepath: string, data: any): any { - // Check if the "auto-generated-language-definitions" directory exists, - // and create it if it doesn't. - if (!fs.existsSync(this.autoGeneratedDir)) { - fs.mkdirSync(this.autoGeneratedDir); - } - - // Write the updated JSON back into the file and add tab indentation - // to make it easier to read. - fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); - } - /** * Read the directory in the given path and return an array of objects with the data of * all extensions found in the directory. @@ -531,7 +502,7 @@ export class Configuration { // If the package.json file exists... if (fs.existsSync(packageJSONPath)) { - const packageJSON = this.readJsonFile(packageJSONPath); + const packageJSON = utils.readJsonFile(packageJSONPath); const id = `${packageJSON.publisher}.${packageJSON.name}`; @@ -692,9 +663,9 @@ export class Configuration { */ private writeCommentLanguageDefinitionsToJsonFile() { // Write the into the single-line-languages.json file. - this.writeJsonFile(this.singleLineLangDefinitionFilePath, this.convertMapToReversedObject(this.singleLineBlocksMap)); + utils.writeJsonFile(this.singleLineLangDefinitionFilePath, utils.convertMapToReversedObject(this.singleLineBlocksMap)); // Write the into the multi-line-languages.json file. - this.writeJsonFile(this.multiLineLangDefinitionFilePath, Object.fromEntries(this.multiLineBlocksMap)); + utils.writeJsonFile(this.multiLineLangDefinitionFilePath, Object.fromEntries(this.multiLineBlocksMap)); } /** @@ -721,7 +692,7 @@ export class Configuration { */ private setLanguageConfiguration(langId: string, multiLine?: boolean, singleLineStyle?: string): vscode.Disposable { const internalLangConfig: vscode.LanguageConfiguration = this.getLanguageConfig(langId); - const defaultMultiLineConfig: any = this.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); + const defaultMultiLineConfig: any = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); let langConfig = {...internalLangConfig}; @@ -798,15 +769,15 @@ export class Configuration { langConfig.onEnterRules.forEach((item) => { // Check if the item has a "beforeText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "beforeText")) { - item.beforeText = this.reconstructRegex(item, "beforeText"); + item.beforeText = utils.reconstructRegex(item, "beforeText"); } // Check if the item has an "afterText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "afterText")) { - item.afterText = this.reconstructRegex(item, "afterText"); + item.afterText = utils.reconstructRegex(item, "afterText"); } // Check if the item has an "afterText" property and reconstruct its regex pattern. if (Object.hasOwn(item, "previousLineText")) { - item.previousLineText = this.reconstructRegex(item, "previousLineText"); + item.previousLineText = utils.reconstructRegex(item, "previousLineText"); } }); } @@ -822,17 +793,17 @@ export class Configuration { // If langConfig has a wordPattern key... if (Object.hasOwn(langConfig, "wordPattern")) { - langConfig.wordPattern = this.reconstructRegex(langConfig, "wordPattern"); + langConfig.wordPattern = utils.reconstructRegex(langConfig, "wordPattern"); } // If langConfig has a folding key... if (Object.hasOwn(langConfig, "folding")) { // @ts-ignore error TS2339: Property 'folding' does not exist on type if (Object.hasOwn(langConfig.folding, "markers")) { // @ts-ignore error TS2339: Property 'folding' does not exist on type - langConfig.folding.markers.start = this.reconstructRegex(langConfig.folding.markers, "start"); + langConfig.folding.markers.start = utils.reconstructRegex(langConfig.folding.markers, "start"); // @ts-ignore error TS2339: Property 'folding' does not exist on type - langConfig.folding.markers.end = this.reconstructRegex(langConfig.folding.markers, "end"); + langConfig.folding.markers.end = utils.reconstructRegex(langConfig.folding.markers, "end"); } } // If langConfig has a indentationRules key... @@ -843,19 +814,19 @@ export class Configuration { for (let key in indentationRules) { // If the key is "increaseIndentPattern", reconstruct the regex pattern. if (key === "increaseIndentPattern") { - indentationRules.increaseIndentPattern = this.reconstructRegex(indentationRules, "increaseIndentPattern"); + indentationRules.increaseIndentPattern = utils.reconstructRegex(indentationRules, "increaseIndentPattern"); } // If the key is "decreaseIndentPattern", reconstruct the regex pattern. if (key === "decreaseIndentPattern") { - indentationRules.decreaseIndentPattern = this.reconstructRegex(indentationRules, "decreaseIndentPattern"); + indentationRules.decreaseIndentPattern = utils.reconstructRegex(indentationRules, "decreaseIndentPattern"); } // If the key is "indentNextLinePattern", reconstruct the regex pattern. if (key === "indentNextLinePattern") { - indentationRules.indentNextLinePattern = this.reconstructRegex(indentationRules, "indentNextLinePattern"); + indentationRules.indentNextLinePattern = utils.reconstructRegex(indentationRules, "indentNextLinePattern"); } // If the key is "unIndentedLinePattern", reconstruct the regex pattern. if (key === "unIndentedLinePattern") { - indentationRules.unIndentedLinePattern = this.reconstructRegex(indentationRules, "unIndentedLinePattern"); + indentationRules.unIndentedLinePattern = utils.reconstructRegex(indentationRules, "unIndentedLinePattern"); } } } @@ -914,96 +885,6 @@ export class Configuration { return merged; } - /** - * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, - * or some patterns are not working as expected. - * - * @param obj The object - * @param key The key to check in the object - * @returns {RegExp} The reconstructed regex pattern. - */ - private reconstructRegex(obj: any, key: string) { - // If key has a "pattern" key, then it's an object... - if (Object.hasOwn(obj[key], "pattern")) { - return new RegExp(obj[key].pattern); - } - // Otherwise it's a string. - else { - return new RegExp(obj[key]); - } - } - - /** - * Convert a Map to an object with it's inner Map's keys and values reversed/switched. - * - * Code based on this StackOverflow answer https://stackoverflow.com/a/45728850/2358222 - * - * @param {Map>} m The Map to convert to an object. - * @returns {object} The converted object. - * - * @example - * reverseMapping( - * Map { - * "supportedLanguages" => Map { - * "apacheconf" => "#", - * "c" => "//", - * "clojure" => ";", - * "coffeescript" => "#", - * "cpp" => "//", - * … - * } - * } - * ); - * - * // Converts to: - * - * { - * "supportedLanguages" => { - * "#": [ - * "apacheconf", - * "coffeescript", - * ... - * ], - * "//": [ - * "c", - * "cpp", - * ... - * ], - * ";": [ - * "clojure", - * ... - * ] - * } - * } - */ - private convertMapToReversedObject(m: Map>): object { - const result: any = {}; - - // Convert a nested key:value Map from inside another Map into an key:array object, - // while reversing/switching the keys and values. The Map's values are now the keys of - // the object and the Map's keys are now added as the values of the array. The reversed - // object is added to the key of the outerMap. - - // Loop through the outer Map... - for (const [key, innerMap] of m.entries()) { - // Convert the inner Map to an object - const o = Object.fromEntries(innerMap); - - // Reverse the inner object mapping. - // - // Loop through the object (o) keys, assigns a new object (r) with the value of the - // object key (k) as the new key (eg. "//") and the new value is an array of all - // the original object keys (o[k]) (eg. "php"). - // If the key (o[k]) already exists in the new object (r), then just add the - // original key to the array, otherwise start a new array ([]) with the original - // key as value ( (r[o[k]] || []).concat(k) ). - // Add this new reversed object to the result object with the outer map key - // as the key. - result[key] = Object.keys(o).reduce((r, k) => Object.assign(r, {[o[k]]: (r[o[k]] || []).concat(k)}), {}); - } - return result; - } - /** * The keyboard binding event handler for the single-line blocks on shift+enter. * @@ -1150,7 +1031,7 @@ export class Configuration { // Log the objects for debugging purposes. this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); this.logger.debug("The language configs found are:", this.languageConfigs); - this.logger.debug("The supported languages for multi-line blocks:", this.readJsonFile(this.multiLineLangDefinitionFilePath)); - this.logger.debug("The supported languages for single-line blocks:", this.readJsonFile(this.singleLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for multi-line blocks:", utils.readJsonFile(this.multiLineLangDefinitionFilePath)); + this.logger.debug("The supported languages for single-line blocks:", utils.readJsonFile(this.singleLineLangDefinitionFilePath)); } } diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..2b124d9 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,122 @@ +import * as fs from "node:fs"; +import * as jsonc from "jsonc-parser"; + +/** + * Read the file and parse the JSON. + * + * @param {string} filepath The path of the file. + * + * @returns The file content. + */ +export function readJsonFile(filepath: string): any { + return jsonc.parse(fs.readFileSync(filepath).toString()); +} + +/** + * Read the file and parse the JSON. + * + * @param {string} filepath The path of the file. + * @param {any} data The data to write into the file. + * @returns The file content. + */ +export function writeJsonFile(filepath: string, data: any): any { + // Check if the "auto-generated-language-definitions" directory exists, + // and create it if it doesn't. + if (!fs.existsSync(this.autoGeneratedDir)) { + fs.mkdirSync(this.autoGeneratedDir); + } + + // Write the updated JSON back into the file and add tab indentation + // to make it easier to read. + fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); +} + +/** + * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, + * or some patterns are not working as expected. + * + * @param obj The object + * @param key The key to check in the object + * @returns {RegExp} The reconstructed regex pattern. + */ +export function reconstructRegex(obj: any, key: string) { + // If key has a "pattern" key, then it's an object... + if (Object.hasOwn(obj[key], "pattern")) { + return new RegExp(obj[key].pattern); + } + // Otherwise it's a string. + else { + return new RegExp(obj[key]); + } +} + +/** + * Convert a Map to an object with it's inner Map's keys and values reversed/switched. + * + * Code based on this StackOverflow answer https://stackoverflow.com/a/45728850/2358222 + * + * @param {Map>} m The Map to convert to an object. + * @returns {object} The converted object. + * + * @example + * reverseMapping( + * Map { + * "supportedLanguages" => Map { + * "apacheconf" => "#", + * "c" => "//", + * "clojure" => ";", + * "coffeescript" => "#", + * "cpp" => "//", + * … + * } + * } + * ); + * + * // Converts to: + * + * { + * "supportedLanguages" => { + * "#": [ + * "apacheconf", + * "coffeescript", + * ... + * ], + * "//": [ + * "c", + * "cpp", + * ... + * ], + * ";": [ + * "clojure", + * ... + * ] + * } + * } + */ +export function convertMapToReversedObject(m: Map>): object { + const result: any = {}; + + // Convert a nested key:value Map from inside another Map into an key:array object, + // while reversing/switching the keys and values. The Map's values are now the keys of + // the object and the Map's keys are now added as the values of the array. The reversed + // object is added to the key of the outerMap. + + // Loop through the outer Map... + for (const [key, innerMap] of m.entries()) { + // Convert the inner Map to an object + const o = Object.fromEntries(innerMap); + + // Reverse the inner object mapping. + // + // Loop through the object (o) keys, assigns a new object (r) with the value of the + // object key (k) as the new key (eg. "//") and the new value is an array of all + // the original object keys (o[k]) (eg. "php"). + // If the key (o[k]) already exists in the new object (r), then just add the + // original key to the array, otherwise start a new array ([]) with the original + // key as value ( (r[o[k]] || []).concat(k) ). + // Add this new reversed object to the result object with the outer map key + // as the key. + result[key] = Object.keys(o).reduce((r, k) => Object.assign(r, {[o[k]]: (r[o[k]] || []).concat(k)}), {}); + } + return result; +} From 813e3c99f6ddd03b9ecfc29ef0b7cbcdf420650c Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 02:52:40 +0100 Subject: [PATCH 34/48] refactor: "directory exists" code in `writeJsonFile` into a new function - Added a new `ensureDirExists` utility function to create specified directory if it doesn't exist. - Moved the "directory exists" code in the `writeJsonFile` util function to the new function, but making it for generic use (removed references to the auto-generated directory). - Added the new function call into the `Configuration::writeCommentLanguageDefinitionsToJsonFile` method to ensure the auto generated dir exists. --- src/configuration.ts | 3 +++ src/utils.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 60ca232..cef0a9c 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -662,6 +662,9 @@ export class Configuration { * either multi-line-languages.json, or single-line-languages.json. */ private writeCommentLanguageDefinitionsToJsonFile() { + // Ensure the auto-generated directory exists. + utils.ensureDirExists(this.autoGeneratedDir); + // Write the into the single-line-languages.json file. utils.writeJsonFile(this.singleLineLangDefinitionFilePath, utils.convertMapToReversedObject(this.singleLineBlocksMap)); // Write the into the multi-line-languages.json file. diff --git a/src/utils.ts b/src/utils.ts index 2b124d9..b19bf57 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,17 +20,22 @@ export function readJsonFile(filepath: string): any { * @returns The file content. */ export function writeJsonFile(filepath: string, data: any): any { - // Check if the "auto-generated-language-definitions" directory exists, - // and create it if it doesn't. - if (!fs.existsSync(this.autoGeneratedDir)) { - fs.mkdirSync(this.autoGeneratedDir); - } - // Write the updated JSON back into the file and add tab indentation // to make it easier to read. fs.writeFileSync(filepath, JSON.stringify(data, null, "\t")); } +/** + * Ensure that the directory exists. If it doesn't exist, create it. + * + * @param {string} dir The directory path to ensure exists. + */ +export function ensureDirExists(dir: string) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } +} + /** * Reconstruct the regex pattern because vscode doesn't like the regex pattern as a string, * or some patterns are not working as expected. From 0ea16093fe9c8680b3b1fc67f14a648315489e05 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 06:22:50 +0100 Subject: [PATCH 35/48] refactor: replace `mergeConfigProperty` with `mergeArraysBy` utils function - Refactored `mergeConfigProperty` into a new generic `mergeArraysBy` utility function. - Removed `mergeConfigProperty` method from `Configuration` class. - Added new `mergeArraysBy` utility function in `utils` file, and updated the code to be more generic with better typings. - Replaced all references to the old method in `Configuration` with the new utils function. --- src/configuration.ts | 48 +++++--------------------------------------- src/utils.ts | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index cef0a9c..47b9a23 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -700,14 +700,10 @@ export class Configuration { let langConfig = {...internalLangConfig}; if (multiLine) { - langConfig.autoClosingPairs = this.mergeConfigProperty( - defaultMultiLineConfig.autoClosingPairs, - internalLangConfig?.autoClosingPairs, - "open" - ); + langConfig.autoClosingPairs = utils.mergeArraysBy(defaultMultiLineConfig.autoClosingPairs, internalLangConfig?.autoClosingPairs, "open"); // Add the multi-line onEnter rules to the langConfig. - langConfig.onEnterRules = this.mergeConfigProperty(Rules.multilineEnterRules, internalLangConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.multilineEnterRules, internalLangConfig?.onEnterRules, "beforeText"); // Only assign the default config comments if it doesn't already exist. // (nullish assignment operator ??=) @@ -736,15 +732,15 @@ export class Configuration { if (isOnEnter && singleLineStyle) { // //-style comments if (singleLineStyle === "//") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.slashEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.slashEnterRules, langConfig?.onEnterRules, "beforeText"); } // #-style comments else if (singleLineStyle === "#") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.hashEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.hashEnterRules, langConfig?.onEnterRules, "beforeText"); } // ;-style comments else if (singleLineStyle === ";") { - langConfig.onEnterRules = this.mergeConfigProperty(Rules.semicolonEnterRules, langConfig?.onEnterRules, "beforeText"); + langConfig.onEnterRules = utils.mergeArraysBy(Rules.semicolonEnterRules, langConfig?.onEnterRules, "beforeText"); } } // If isOnEnter is false AND singleLineStyle isn't false, i.e. a string. @@ -854,40 +850,6 @@ export class Configuration { return vscode.languages.setLanguageConfiguration(langId, langConfig); } - /** - * Merges two configuration properties arrays, removing any duplicates based on a - * specified property. - * - * @param {any[]} defaultConfigProperty The default configuration property array of objects. - * @param {any[]} internalConfigProperty The internal configuration property array of objects. - * @param {string} objectKey The key within the array item object to check against for preventing duplicates - * @returns {any[]} The merged configuration property array without duplicates. - */ - private mergeConfigProperty(defaultConfigProperty: any[], internalConfigProperty: any[], objectKey: string) { - // Define an empty array if the internalConfigProperty is undefined. - internalConfigProperty ??= []; - - // Copy to avoid side effects. - const merged = [...defaultConfigProperty]; - - /** - * Merge the arrays and remove any duplicates. - */ - - // Loop over the internalConfigProperty array... - internalConfigProperty.forEach((item) => - // Test all items in the merged array, and if the item's - // key is not already present in one of the merged array's objects then add the item - // to the merged array. - // - // Code based on "2023 update" portion of this StackOverflow answer: - // https://stackoverflow.com/a/1584377/2358222 - merged.some((mergedItem) => item[objectKey] === mergedItem[objectKey]) ? null : merged.push(item) - ); - - return merged; - } - /** * The keyboard binding event handler for the single-line blocks on shift+enter. * diff --git a/src/utils.ts b/src/utils.ts index b19bf57..ccbe6b5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -125,3 +125,46 @@ export function convertMapToReversedObject(m: Map>): } return result; } + +/** + * Merges two arrays of objects, removing duplicates based on a specified property. + * + * Code based on "2023 update" portion of this StackOverflow answer: + * https://stackoverflow.com/a/1584377/2358222 + * + * @param {T[]} primaryArray The primary array of objects (takes precedence). + * @param {T[]} secondaryArray The secondary array of objects to merge in. + * @param {keyof T} key The property key to check for duplicates. + * + * @returns {T[]} The merged array without duplicates + * + * @example + * const users1 = [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]; + * const users2 = [{id: 2, name: 'Jane'}, {id: 3, name: 'Jane Doe'}]; + * const merged = mergeArraysBy(users1, users2, 'name'); + * // Result: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}, {id: 3, name: 'Jane Doe'}] + */ +export function mergeArraysBy(primaryArray: T[], secondaryArray: T[], key: keyof T): T[] { + // Handle undefined/null arrays + const primary = primaryArray || []; + const secondary = secondaryArray || []; + + // Start with primary array (avoids side effects) + const merged = [...primary]; + + // Add items from secondary array that don't exist in primary, + // removing any duplicates. + secondary.forEach((item) => { + // Test all items in the merged array to check if the value of the key + // already exists in the merged array. + const exists: boolean = merged.some((existingItem) => item[key] === existingItem[key]); + + // If the value of the key does not exist in the merged array, + // then add the item, which prevents duplicates. + if (!exists) { + merged.push(item); + } + }); + + return merged; +} From 397fba12ca1c5e160d75f1f23fb7524f87a7db79 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 08:09:45 +0100 Subject: [PATCH 36/48] refactor: retrieval of extension data into a new `ExtensionData` class. Changed: - Moved `getExtensionPackageJsonData`, `setExtensionData`, `getExtensionData` methods into a new `ExtensionData` class for simplicity and better organisation. - Simplified the `getExtensionPackageJsonData` method to get all package.json data not just a select few. By using the new type `IPackageJson`, we can properly type the data as package.json instead of a generic `object` or `any`. - Changed the `name` variable to `namespace`, simplied it's code and added it to the `contributes.configuration` object of the packageJson data, since it's the namespace of the settings - Changed the `name` variable to `namespace`, simplied it's code and added it to the `contributes.configuration` object of the packageJson data, since it's the namespace of the configuration settings. - Split `setExtensionData` into 2 methods: `setExtensionData` and `createExtensionData`; and simplified the code. - `createExtensionData` to create the extension data object, defining the keys and values for the Map. The keys as defined here will also be used for type inference for VScode intellisense in the new `get` method. - `setExtensionData` to set the data as defined in `createExtensionData` method into the `extensionData` Map. - Changed all references to the old `getExtensionData` method to use the `get` method of the new `ExtensionData` class. - Renamed the `Configuration` property `extensionDetails` to `extensionData`; and instantiate the `ExtensionData` class as it's value. Added: - Added new `get` method to get the extension's data by a specified key. VScode intellisense auto-suggests the keys by using type inference from the keys set in `createExtensionData` method. This is important so remembering or looking up the keys isn't a pain. - Added `getAll` method to get all extension data as readonly so it can only be logged in the Output Channel. - Added a new dependency `package-json-type` to allow package.json typings as `IPackageJson`. --- package.json | 3 +- src/configuration.ts | 107 +++++++---------------------------------- src/extension.ts | 7 ++- src/extensionData.ts | 110 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 src/extensionData.ts diff --git a/package.json b/package.json index 5aaf61d..401dbbc 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ }, "dependencies": { "is-wsl": "^3.1.0", - "jsonc-parser": "^3.3.1" + "jsonc-parser": "^3.3.1", + "package-json-type": "^1.0.3" } } diff --git a/src/configuration.ts b/src/configuration.ts index 47b9a23..159273e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -9,6 +9,7 @@ import isWsl from "is-wsl"; import {Rules} from "./rules"; import {Logger} from "./logger"; import * as utils from "./utils"; +import {ExtensionData} from "./extensionData"; export class Configuration { /************** @@ -23,9 +24,10 @@ export class Configuration { private logger: Logger; /** - * This extension details in the form of a key:value Map object, for ease of use. + * This extension data class instance. + * @type {ExtensionData} */ - private extensionDetails = new Map(); + private extensionData: ExtensionData = new ExtensionData(); /** * A key:value Map object of language IDs and their config file paths. @@ -66,10 +68,8 @@ export class Configuration { public constructor(logger: Logger) { this.logger = logger; - this.setExtensionData(); - // Always output extension information to channel on activate. - this.logger.debug(`Extension details:`, this.extensionDetails); + this.logger.debug(`Extension details:`, this.extensionData.getAll()); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -198,86 +198,13 @@ export class Configuration { } } - /** - * Get the names, id, and version of this extension from package.json. - * - * @returns {object} An object containing the extension id, name, display name, and version. - */ - private getExtensionPackageJsonData(): {id: string; name: string; displayName: string; version: string} { - const packageJSON = utils.readJsonFile(__dirname + "/../../package.json"); - - const displayName: string = packageJSON.displayName; - const fullname: string = packageJSON.name; - const id: string = `${packageJSON.publisher}.${fullname}`; - const version: string = packageJSON.version; - - let nameParts = fullname.split("-"); - nameParts[0] = "auto"; - const name = nameParts.join("-"); - - return {id: id, name: name, displayName: displayName, version: version}; - } - - /** - * Set the extension data into the extensionDetails Map. - */ - private setExtensionData() { - const extensionPackageJsonData = this.getExtensionPackageJsonData(); - - const id = extensionPackageJsonData.id; - const name = extensionPackageJsonData.name; - const displayName = extensionPackageJsonData.displayName; - const version = extensionPackageJsonData.version; - - // The path to the user extensions. - const userExtensionsPath = isWsl - ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); - - // The path to the built-in extensions. - // This env variable changes when on WSL to it's WSL-built-in extensions path. - const builtInExtensionsPath = path.join(vscode.env.appRoot, "extensions"); - - this.extensionDetails.set("id", id); - this.extensionDetails.set("name", name); - this.extensionDetails.set("displayName", displayName); - this.extensionDetails.set("version", version); - this.extensionDetails.set("userExtensionsPath", userExtensionsPath); - this.extensionDetails.set("builtInExtensionsPath", builtInExtensionsPath); - - if (isWsl) { - // Get the root path to VS Code from the env variable, and use it to get - // the Windows built-in extensions. - const windowsBuiltInExtensionsPathFromWsl = path.join(process.env.VSCODE_CWD, "resources/app/extensions"); - - // Get the Windows user extensions path from env variable. - const windowsUserExtensionsPathFromWsl = path.dirname(process.env.VSCODE_WSL_EXT_LOCATION); - - this.extensionDetails.set("WindowsUserExtensionsPathFromWsl", windowsUserExtensionsPathFromWsl); - this.extensionDetails.set("WindowsBuiltInExtensionsPathFromWsl", windowsBuiltInExtensionsPathFromWsl); - } - } - - /** - * Get the extension's details. - * - * @param {string} key The key of the specific extension detail to get. - * - * @returns {any} Returns a value of a specific key. - */ - public getExtensionData(key: string): any { - if (this.extensionDetails.has(key)) { - return this.extensionDetails.get(key); - } - } - /** * Get all the extension's configuration settings. * * @returns {vscode.WorkspaceConfiguration} */ public getConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration(this.getExtensionData("name"), null); + return vscode.workspace.getConfiguration(this.extensionData.get("name"), null); } /** @@ -368,15 +295,15 @@ export class Configuration { // If running in WSL... if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); // Read the paths and create arrays of the extensions. - const builtInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); - const userExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); + const windowsBuiltInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); + const windowsUserExtensions = this.readExtensionsFromDirectory(windowsUserExtensionsPath); // Combine the built-in and user extensions into the extensions array. - extensions.push(...builtInExtensions, ...userExtensions); + extensions.push(...windowsBuiltInExtensions, ...windowsUserExtensions); } // Add all installed extensions (including built-in ones) into the extensions array. @@ -916,7 +843,7 @@ export class Configuration { */ private handleChangeBladeMultiLineBlock(textEditor: vscode.TextEditor) { let langId = textEditor.document.languageId; - const extensionName = this.getExtensionData("name"); + const extensionName = this.extensionData.get("name"); // Only carry out function if languageId is blade. if (langId === "blade" && !this.isLangIdDisabled(langId)) { @@ -955,25 +882,25 @@ export class Configuration { private logDebugInfo() { // The path to the built-in extensions. The env variable changes when on WSL. // So we can use it for both Windows and WSL. - const builtInExtensionsPath = this.getExtensionData("builtInExtensionsPath"); + const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); let extensionsPaths = {}; if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.getExtensionData("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.getExtensionData("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); extensionsPaths = { "Windows-installed Built-in Extensions Path": windowsBuiltInExtensionsPath, "Windows-installed User Extensions Path": windowsUserExtensionsPath, "WSL-installed Built-in Extensions Path": builtInExtensionsPath, - "WSL-installed User Extensions Path": this.getExtensionData("userExtensionsPath"), + "WSL-installed User Extensions Path": this.extensionData.get("userExtensionsPath"), }; } else { extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": this.getExtensionData("userExtensionsPath"), + "User Extensions Path": this.extensionData.get("userExtensionsPath"), }; } diff --git a/src/extension.ts b/src/extension.ts index 20eefd1..0a771856 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,8 +4,10 @@ import * as vscode from "vscode"; import {Configuration} from "./configuration"; import {Logger} from "./logger"; +import {ExtensionData} from "./extensionData"; const logger = new Logger(); +const extensionData = new ExtensionData(); logger.setupOutputChannel(); let configuration = new Configuration(logger); @@ -17,8 +19,9 @@ export function activate(context: vscode.ExtensionContext) { disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); - const extensionName = configuration.getExtensionData("name"); - const extensionDisplayName = configuration.getExtensionData("displayName"); + const extensionName = extensionData.get("name"); + + const extensionDisplayName = extensionData.get("displayName"); let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); diff --git a/src/extensionData.ts b/src/extensionData.ts new file mode 100644 index 0000000..1f63bd3 --- /dev/null +++ b/src/extensionData.ts @@ -0,0 +1,110 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import isWsl from "is-wsl"; +import {IPackageJson} from "package-json-type"; + +import {readJsonFile} from "./utils"; + +export class ExtensionData { + /** + * This extension details in the form of a key:value Map object. + * + * @type {Map} + */ + private extensionData = new Map(); + + /** + * The package.json data for this extension. + * + * @type {IPackageJson} + */ + private packageJsonData: IPackageJson; + + public constructor() { + this.packageJsonData = this.getExtensionPackageJsonData(); + this.setExtensionData(); + } + + /** + * Get the names, id, and version of this extension from package.json. + * + * @returns {IPackageJson} The package.json data for this extension, plus the new `id`, and `contributes.configuration.namespace` keys. + */ + private getExtensionPackageJsonData(): IPackageJson { + const packageJSON: IPackageJson = readJsonFile(__dirname + "/../../package.json"); + + // Set the id (publisher.name) into the packageJSON object as a new `id` key. + packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; + + // The configuration settings namespace is a shortened version of the extension name. + // We just need to replace "automatic" with "auto" in the name. + const settingsNamespace: string = packageJSON.name.replace("automatic", "auto"); + // Set the namespace to the packageJSON `configuration` object as a new `namespace` key. + packageJSON.contributes.configuration.namespace = settingsNamespace; + + return packageJSON; + } + + /** + * Set the extension data into the extensionData Map. + */ + private setExtensionData() { + // Set all entries in the extensionData Map. + Object.entries(this.createExtensionData()).forEach(([key, value]) => { + this.extensionData.set(key, value); + }); + } + + /** + * Create the extension data object for the extensionData Map. + * It also helps for type inference intellisense in the get method. + * + * @returns The extension data object with keys and values. + */ + private createExtensionData() { + // The path to the user extensions. + const userExtensionsPath = isWsl + ? path.join(vscode.env.appRoot, "../../", "extensions") + : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); + + + // Set the keys and values for the Map. + // The keys will also be used for type inference in VSCode intellisense. + return { + id: this.packageJsonData.id, + name: this.packageJsonData.contributes.configuration.namespace, + displayName: this.packageJsonData.displayName, + version: this.packageJsonData.version, + userExtensionsPath: userExtensionsPath, + // The path to the built-in extensions. + // This env variable changes when on WSL to it's WSL-built-in extensions path. + builtInExtensionsPath: path.join(vscode.env.appRoot, "extensions"), + + // Only set these if running in WSL. + ...(isWsl && { + WindowsUserExtensionsPathFromWsl: path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!), + WindowsBuiltInExtensionsPathFromWsl: path.join(process.env.VSCODE_CWD!, "resources/app/extensions"), + }), + } as const; + } + + /** + * Get the extension's data by a specified key. + * + * @param {K} key The key of the extension detail to get. + * + * @returns {ReturnType[K] | undefined} The value of the extension detail, or undefined if the key does not exist. + */ + public get>(key: K): ReturnType[K] | undefined { + return this.extensionData.get(key) as ReturnType[K] | undefined; + } + + /** + * Get all extension data. + * + * @returns {ReadonlyMap} A read-only Map containing all extension details. + */ + public getAll(): ReadonlyMap { + return this.extensionData; + } +} From 10b8f80e856676a72180d1f80b34033006604d42 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 08:44:11 +0100 Subject: [PATCH 37/48] fix: `vscode.extensions.all` API doesn't include any disabled extensions VS Code's `extensions.all` API only gets enabled extensions and doesn't include disabled ones, which could prevent language configs being found. To fix, we completely remove getting the extensions from the vscode API, and instead get the extensions directly from the directories. - Added the ability to get the built-in and user extensions directly from the directory on non-WSL systems (eg. Windows), but using the same way it gets extensions on WSL using the extension data path keys and `readExtensionsFromDirectory` method. - Removed `vscode.extensions.all` API call from `findAllLanguageConfigFilePaths` method. --- src/configuration.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 159273e..995f1f6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -306,9 +306,16 @@ export class Configuration { extensions.push(...windowsBuiltInExtensions, ...windowsUserExtensions); } + const userExtensionsPath = this.extensionData.get("userExtensionsPath"); + const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); + + // Read the paths and create arrays of the extensions. + const userExtensions = this.readExtensionsFromDirectory(userExtensionsPath); + const builtInExtensions = this.readExtensionsFromDirectory(builtInExtensionsPath); + // Add all installed extensions (including built-in ones) into the extensions array. // If running WSL, these will be the WSL-installed extensions. - extensions.push(...vscode.extensions.all); + extensions.push(...builtInExtensions, ...userExtensions); // Loop through all installed extensions, including built-in extensions for (let extension of extensions) { From 518bfea483357303e87991f036247d853d757034 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Thu, 17 Jul 2025 23:04:18 +0100 Subject: [PATCH 38/48] fix: TS compile error: Cannot find name 'id'. https://github.com/yCodeTech/auto-comment-blocks/actions/runs/16339281667/job/46157703386?pr=12 This error occurs because testing used a hard-coded path to simulate what the `vscode.extensions.getExtension().extensionPath` API would get. Since commit b0a40aa changed the way we get/set the package.json data, this was just an oversight. Since we're already using the path of the extension to get the package.json in `getExtensionPackageJsonData`, we can just leverage this, and properly integrate the path. Fixed by: - Adding the extension path as a new `extensionPath` key in the `packageJSON` data object. - Completely removing the VS Code's `.extensions.getExtension()` API call in `createExtensionData` method and changing it to use the `extensionPath` key in the `packageJsonData` property. --- src/extensionData.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 1f63bd3..473513a 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -28,13 +28,16 @@ export class ExtensionData { /** * Get the names, id, and version of this extension from package.json. * - * @returns {IPackageJson} The package.json data for this extension, plus the new `id`, and `contributes.configuration.namespace` keys. + * @returns {IPackageJson} The package.json data for this extension, with extra custom keys. */ private getExtensionPackageJsonData(): IPackageJson { - const packageJSON: IPackageJson = readJsonFile(__dirname + "/../../package.json"); + const extensionPath = path.join(__dirname, "../../"); + + const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json")); // Set the id (publisher.name) into the packageJSON object as a new `id` key. packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; + packageJSON.extensionPath = extensionPath; // The configuration settings namespace is a shortened version of the extension name. // We just need to replace "automatic" with "auto" in the name. @@ -65,8 +68,7 @@ export class ExtensionData { // The path to the user extensions. const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(vscode.extensions.getExtension(id).extensionPath, "../"); - + : path.join(this.packageJsonData.extensionPath, "../"); // Set the keys and values for the Map. // The keys will also be used for type inference in VSCode intellisense. From 027c0da5a6100fa17019ab0a2688530bb8b8b342 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 18 Jul 2025 00:45:53 +0100 Subject: [PATCH 39/48] fix: update the rest of `packageJSON` variable typings to `IPackageJson` --- src/configuration.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 995f1f6..241cee8 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -5,6 +5,7 @@ import * as vscode from "vscode"; import * as fs from "node:fs"; import * as path from "path"; import isWsl from "is-wsl"; +import {IPackageJson} from "package-json-type"; import {Rules} from "./rules"; import {Logger} from "./logger"; @@ -319,7 +320,7 @@ export class Configuration { // Loop through all installed extensions, including built-in extensions for (let extension of extensions) { - const packageJSON = extension.packageJSON; + const packageJSON: IPackageJson = extension.packageJSON; // If an extension package.json has "contributes" key, // AND the contributes object has "languages" key... @@ -415,11 +416,11 @@ export class Configuration { * * @param {string} extensionsPath The path where extensions are stored. * - * @returns {Array<{ id: string; extensionPath: string; packageJSON: any }>} + * @returns {Array<{ id: string; extensionPath: string; packageJSON: IPackageJson }>} */ - private readExtensionsFromDirectory(extensionsPath: string): Array<{id: string; extensionPath: string; packageJSON: any}> { + private readExtensionsFromDirectory(extensionsPath: string): Array<{id: string; extensionPath: string; packageJSON: IPackageJson}> { // Create an array to hold the found extensions. - const foundExtensions: Array<{id: string; extensionPath: string; packageJSON: any}> = []; + const foundExtensions: Array<{id: string; extensionPath: string; packageJSON: IPackageJson}> = []; fs.readdirSync(extensionsPath).forEach((extensionName) => { const extensionPath = path.join(extensionsPath, extensionName); @@ -436,7 +437,7 @@ export class Configuration { // If the package.json file exists... if (fs.existsSync(packageJSONPath)) { - const packageJSON = utils.readJsonFile(packageJSONPath); + const packageJSON: IPackageJson = utils.readJsonFile(packageJSONPath); const id = `${packageJSON.publisher}.${packageJSON.name}`; From 3f8784bf5ec76e0451b0a8664f7c16af6206a82a Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 18 Jul 2025 00:52:02 +0100 Subject: [PATCH 40/48] remove: unused `context` param. --- src/configuration.ts | 3 +-- src/extension.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 241cee8..dcdbda2 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -85,10 +85,9 @@ export class Configuration { /** * Configure the comment blocks. * - * @param {vscode.ExtensionContext} context The context of the extension. * @returns {vscode.Disposable[]} */ - public configureCommentBlocks(context: vscode.ExtensionContext) { + public configureCommentBlocks() { const disposables: vscode.Disposable[] = []; /** diff --git a/src/extension.ts b/src/extension.ts index 0a771856..a5c2c2a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,7 +14,7 @@ let configuration = new Configuration(logger); const disposables: vscode.Disposable[] = []; export function activate(context: vscode.ExtensionContext) { - const configureCommentBlocksDisposable = configuration.configureCommentBlocks(context); + const configureCommentBlocksDisposable = configuration.configureCommentBlocks(); const registerCommandsDisposable = configuration.registerCommands(); disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); @@ -155,7 +155,7 @@ export function activate(context: vscode.ExtensionContext) { // Called when active editor language is changed, so re-configure the comment blocks. vscode.workspace.onDidOpenTextDocument(() => { logger.info("Active editor language changed, re-configuring comment blocks."); - const configureCommentBlocksDisposable = configuration.configureCommentBlocks(context); + const configureCommentBlocksDisposable = configuration.configureCommentBlocks(); disposables.push(...configureCommentBlocksDisposable); }); From 13cca3958af1644f7cf86339ede095502ccf7deb Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 18 Jul 2025 00:55:05 +0100 Subject: [PATCH 41/48] style: formatting and adding docblocks --- src/configuration.ts | 12 ++++++++++++ src/utils.ts | 17 +++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index dcdbda2..8e0c3a0 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -56,10 +56,22 @@ export class Configuration { */ private multiLineBlocksMap: Map = new Map(); + /** + * The directory where the auto-generated language definitions are stored. + * @type {string} + */ private readonly autoGeneratedDir = `${__dirname}/../../auto-generated-language-definitions`; + /** + * The file path for the single-line language definitions. + * @type {string} + */ private readonly singleLineLangDefinitionFilePath = `${this.autoGeneratedDir}/single-line-languages.json`; + /** + * The file path for the multi-line language definitions. + * @type {string} + */ private readonly multiLineLangDefinitionFilePath = `${this.autoGeneratedDir}/multi-line-languages.json`; /*********** diff --git a/src/utils.ts b/src/utils.ts index ccbe6b5..1ffffeb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -81,20 +81,9 @@ export function reconstructRegex(obj: any, key: string) { * * { * "supportedLanguages" => { - * "#": [ - * "apacheconf", - * "coffeescript", - * ... - * ], - * "//": [ - * "c", - * "cpp", - * ... - * ], - * ";": [ - * "clojure", - * ... - * ] + * "#": ["apacheconf", "coffeescript"], + * "//": ["c", "cpp"], + * ";": ["clojure"] * } * } */ From a46651b4668e12a42817bc9995493fce5b2f6999 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 18 Jul 2025 02:10:25 +0100 Subject: [PATCH 42/48] fix: `disabledLanguages` are not being disabled Languages defined in the `disabledLanguages` setting are not being disabled for certain things, and are still being treated as "supported". Disabled languages should not be in any "supported" list, auto or custom supported. Fixed by ensuring in all cases the langId will be ignored if it's set as disabled. - Added `isLangIdDisabled` method calls to all single-line comments and multi-line comments for both auto-supported and custom-supported lists. --- src/configuration.ts | 50 ++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 8e0c3a0..7e9ba4f 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -111,6 +111,7 @@ export class Configuration { // Setup the auto-supported single-line languages. for (let [langId, style] of singleLineLangs) { + // If langId isn't disabled... if (!this.isLangIdDisabled(langId)) { // Set a bool if the single-line language also supports multi-line comments // (ie. the single-line language is also present in the multi-line map); @@ -137,16 +138,20 @@ export class Configuration { // Setup the custom-supported single-line languages, that are otherwise unsupported. for (let [langId, style] of customSingleLineLangs) { - // Set a bool if the single-line language also supports multi-line comments - // (ie. the single-line language is also present in the multi-line map); - let multiLine = customMultiLineLangs.includes(langId); - disposables.push(this.setLanguageConfiguration(langId, multiLine, style)); + // If the langId isn't set as disabled... + if (!this.isLangIdDisabled(langId)) { + // Set a bool if the single-line language also supports multi-line comments + // (ie. the single-line language is also present in the multi-line map); + let multiLine = customMultiLineLangs.includes(langId); + disposables.push(this.setLanguageConfiguration(langId, multiLine, style)); + } } // Setup the custom-supported multi-line languages, that are otherwise unsupported. for (let langId of customMultiLineLangs) { - // If customSingleLineLangs doesn't have the langId - if (!customSingleLineLangs.has(langId)) { + // If customSingleLineLangs doesn't have the langId AND + // the langId isn't set as disabled... + if (!customSingleLineLangs.has(langId) && !this.isLangIdDisabled(langId)) { disposables.push(this.setLanguageConfiguration(langId, true)); } } @@ -495,8 +500,10 @@ export class Configuration { if (config.comments.blockComment.includes("/*")) { // console.log(langId, config.comments); - // If Language ID isn't already in the langArray... - if (!langArray.includes(langId)) { + // If Language ID isn't already in the langArray AND + // the langId isn't set as disabled... + if (!langArray.includes(langId) && !this.isLangIdDisabled(langId)) { + // Add it to the array. langArray.push(langId); } } @@ -513,9 +520,10 @@ export class Configuration { langArray = []; for (let langId of multiLineStyleBlocksLangs) { // If langId is exists (ie. not NULL or empty string) AND - // the array doesn't already include langId, - // then add it to the array. - if (langId && !langArray.includes(langId)) { + // the array doesn't already include langId, AND + // the langId isn't set as disabled... + if (langId && !langArray.includes(langId) && !this.isLangIdDisabled(langId)) { + // Add it to the array. langArray.push(langId); } } @@ -558,9 +566,10 @@ export class Configuration { style = ";"; } - // If style any empty string, (i.e. not an unsupported single-line - // comment like bat's @rem)... - if (style != "") { + // If style is NOT an empty string, (i.e. not an unsupported single-line + // comment like bat's @rem), AND + // the langId isn't set as disabled... + if (style != "" && !this.isLangIdDisabled(langId)) { // Set the langId and it's style into the Map. tempMap.set(langId, style); } @@ -577,6 +586,9 @@ export class Configuration { // Get user-customized langIds for the //-style and add to the map. let customSlashLangs = this.getConfigurationValue("slashStyleBlocks"); for (let langId of customSlashLangs) { + // If langId is exists (ie. not NULL or empty string) AND + // the langId is longer than 0, AND + // the langId isn't set as disabled... if (langId && langId.length > 0) { tempMap.set(langId, "//"); } @@ -585,7 +597,10 @@ export class Configuration { // Get user-customized langIds for the #-style and add to the map. let customHashLangs = this.getConfigurationValue("hashStyleBlocks"); for (let langId of customHashLangs) { - if (langId && langId.length > 0) { + // If langId is exists (ie. not NULL or empty string) AND + // the langId is longer than 0, AND + // the langId isn't set as disabled... + if (langId && langId.length > 0 && !this.isLangIdDisabled(langId)) { tempMap.set(langId, "#"); } } @@ -593,7 +608,10 @@ export class Configuration { // Get user-customized langIds for the ;-style and add to the map. let customSemicolonLangs = this.getConfigurationValue("semicolonStyleBlocks"); for (let langId of customSemicolonLangs) { - if (langId && langId.length > 0) { + // If langId is exists (ie. not NULL or empty string) AND + // the langId is longer than 0, AND + // the langId isn't set as disabled... + if (langId && langId.length > 0 && !this.isLangIdDisabled(langId)) { tempMap.set(langId, ";"); } } From 9800b7049adc252bca601cf941c52de850866355 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 06:11:52 +0100 Subject: [PATCH 43/48] docs: update language definition files - Update auto-supported definition files for the VScode default languages. - Remove custom-supported languages. - Added links to the definition files in README to view all auto-supported VScode default languages. --- README.md | 7 ++++++- .../multi-line-languages.json | 5 +---- .../single-line-languages.json | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 656cf7f..e04d1f6 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,12 @@ There are 3 conditions in which a language is officially supported: 2. The language is not defined in the `skip-languages` config file; and 3. The language config has either `lineComment` or `blockComment` keys defined. -Most of the officially VScode-supported languages (as defined in the [docs](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers)) pass these conditions. +Most of the officially VScode default languages (as defined in the [docs](https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers)) pass these conditions. + +For a full list of auto-supported VScode default languages, please view the auto-generated definition files: + +- [Multi-line languages](https://github.com/yCodeTech/auto-comment-blocks/blob/master/auto-generated-language-definitions/multi-line-languages.json) +- [Single-line languages](https://github.com/yCodeTech/auto-comment-blocks/blob/master/auto-generated-language-definitions/single-line-languages.json) --- diff --git a/auto-generated-language-definitions/multi-line-languages.json b/auto-generated-language-definitions/multi-line-languages.json index 1e690d9..4413b4f 100644 --- a/auto-generated-language-definitions/multi-line-languages.json +++ b/auto-generated-language-definitions/multi-line-languages.json @@ -30,8 +30,5 @@ "typescript", "typescriptreact" ], - "customSupportedLanguages": [ - "blade", - "html" - ] + "customSupportedLanguages": [] } \ No newline at end of file diff --git a/auto-generated-language-definitions/single-line-languages.json b/auto-generated-language-definitions/single-line-languages.json index 9f6a72c..fc99f85 100644 --- a/auto-generated-language-definitions/single-line-languages.json +++ b/auto-generated-language-definitions/single-line-languages.json @@ -11,7 +11,6 @@ "git-commit", "git-rebase", "github-actions-workflow", - "http", "ignore", "julia", "makefile", @@ -58,7 +57,8 @@ ], ";": [ "clojure", - "ini" + "ini", + "wat" ] }, "customSupportedLanguages": {} From 692895e5dc16801c39bf276207173af8e7338588 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 07:20:03 +0100 Subject: [PATCH 44/48] fix: `languageConfigFilePaths` to support multiple config paths per lang Due to some extensions either not specifying all the language configuration properties in the file or setting the comment properties to an empty array (like the "stillat-llc.vscode-antlers" extension does which overrides the HTML config and makes the comments not work without the extension activated), we need to ensure that they are merged with other possible configs for the language. - Changed `languageConfigFilePaths` Map to accept an array value instead of just a single string. - Changed `findAllLanguageConfigFilePaths` method to set the config path into an array as the value in `languageConfigFilePaths` Map, and to push a new path into the array if the `langId` key already exists in the Map. - Changed `setLanguageConfigDefinitions` method to allow it to loop through the new array of paths which sets the configs into the `languageConfigs` Map, and if the `langId` key already exists then merge the existing config with the new config. This merges the comments from both configs together and skips empty arrays. It also merges all other config properties together to ensure we are getting a full language configuration. By merging all available configs for the language, the language won't have any issues regarding malformed configs, or comments not working. --- src/configuration.ts | 129 +++++++++++++++++++++++++++++-------------- 1 file changed, 88 insertions(+), 41 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 7e9ba4f..7437c35 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -33,7 +33,7 @@ export class Configuration { /** * A key:value Map object of language IDs and their config file paths. */ - private languageConfigFilePaths = new Map(); + private languageConfigFilePaths = new Map(); /** * A key:value Map object of language IDs and their configs. @@ -343,23 +343,33 @@ export class Configuration { if (Object.hasOwn(packageJSON, "contributes") && Object.hasOwn(packageJSON.contributes, "languages")) { // Loop through the languages... for (let language of packageJSON.contributes.languages) { + const langId = language.id; // Get the languages to skip. let skipLangs = this.getLanguagesToSkip(); - // If skipLangs doesn't include the language ID, + // If skipLangs doesn't include the langId, // AND the language object has "configuration" key... - if (!skipLangs?.includes(language.id) && Object.hasOwn(language, "configuration")) { + if (!skipLangs?.includes(langId) && Object.hasOwn(language, "configuration")) { // Join the extension path with the configuration path. let configPath = path.join(extension.extensionPath, language.configuration); - // Set the language ID and config path into the languageConfigFilePaths Map. - this.languageConfigFilePaths.set(language.id, configPath); + + // If the langId already exists... + if (this.languageConfigFilePaths.has(langId)) { + // Push the new config path into the array of the existing langId. + this.languageConfigFilePaths.get(langId).push(configPath); + } + // Otherwise, if the langId doesn't exist... + else { + // Set the langId with a new config path array. + this.languageConfigFilePaths.set(langId, [configPath]); + } } } } } // Set the languageConfigFilePaths to a new map with all the languages sorted in - // ascending order,for sanity reasons. + // ascending order, for sanity reasons. this.languageConfigFilePaths = new Map([...this.languageConfigFilePaths].sort()); } @@ -367,46 +377,83 @@ export class Configuration { * Set the language config definitions. */ private setLanguageConfigDefinitions() { - this.languageConfigFilePaths.forEach((filepath, langId) => { - const config = utils.readJsonFile(filepath); - - // If the config JSON has more than 0 keys (ie. not empty) - if (Object.keys(config).length > 0) { - /** - * Change all autoClosingPairs items that are using the simpler syntax - * (array instead of object) into the object with open and close keys. - * Prevents vscode from failing quietly and not changing the editor language - * properly, which makes the open file become unresponsive when changing tabs. - */ - - // If config has key autoClosingPairs... - if (Object.hasOwn(config, "autoClosingPairs")) { - // Define a new array as the new AutoClosingPair. - const autoClosingPairsArray: vscode.AutoClosingPair[] = []; - // Loop through the config's autoClosingPairs... - config.autoClosingPairs.forEach((item) => { - // If the item is an array... - if (Array.isArray(item)) { - // Create a new object with the 1st array element [0] as the - // value of the open key, and the 2nd element [1] as the value - // of the close key. - const autoClosingPairsObj = {open: item[0], close: item[1]}; - // Push the object into the new array. - autoClosingPairsArray.push(autoClosingPairsObj); + this.languageConfigFilePaths.forEach((paths, langId) => { + // Loop through the paths array... + paths.forEach((filepath) => { + let config = utils.readJsonFile(filepath); + + // If the config JSON has more than 0 keys (ie. not empty) + if (Object.keys(config).length > 0) { + /** + * Change all autoClosingPairs items that are using the simpler syntax + * (array instead of object) into the object with open and close keys. + * Prevents vscode from failing quietly and not changing the editor language + * properly, which makes the open file become unresponsive when changing tabs. + */ + + // If config has key autoClosingPairs... + if (Object.hasOwn(config, "autoClosingPairs")) { + // Define a new array as the new AutoClosingPair. + const autoClosingPairsArray: vscode.AutoClosingPair[] = []; + // Loop through the config's autoClosingPairs... + config.autoClosingPairs.forEach((item) => { + // If the item is an array... + if (Array.isArray(item)) { + // Create a new object with the 1st array element [0] as the + // value of the open key, and the 2nd element [1] as the value + // of the close key. + const autoClosingPairsObj = {open: item[0], close: item[1]}; + // Push the object into the new array. + autoClosingPairsArray.push(autoClosingPairsObj); + } + // Otherwise, the item is an object, so just push it into the array. + else { + autoClosingPairsArray.push(item); + } + }); + + // Add the new array to the config's autoClosingPairs key. + config.autoClosingPairs = autoClosingPairsArray; + } + + // If the langId already exists, then it has multiple config files from + // different extensions, so we need to merge them together to ensure + // a full configuration is set, to avoid issues of missing values. + if (this.languageConfigs.has(langId)) { + const existingConfig = this.languageConfigs.get(langId); + + // Only merge if both configs have comments + if (existingConfig.comments && config.comments) { + // Start with existing comments as base + const mergedComments = {...existingConfig.comments}; + + // Merge each comment type from new config. + Object.entries(config.comments).forEach(([key, value]) => { + // Skip empty arrays. + if (Array.isArray(value) && value.length === 0) { + return; + } + mergedComments[key] = value; + }); + + // Update the config with merged comments + config = { + ...existingConfig, + ...config, + comments: mergedComments, + }; } - // Otherwise, the item is an object, so just push it into the array. + // If only one config has comments or neither has comments... else { - autoClosingPairsArray.push(item); + // Just merge the configs directly. + config = {...existingConfig, ...config}; } - }); + } - // Add the new array to the config's autoClosingPairs key. - config.autoClosingPairs = autoClosingPairsArray; + // Set the language configs into the Map. + this.languageConfigs.set(langId, config); } - - // Set the language configs into the Map. - this.languageConfigs.set(langId, config); - } + }); }); } From 90eb99d9b7c2a57d5463baee2ac85df5f8546352 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 07:20:43 +0100 Subject: [PATCH 45/48] chore: version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 401dbbc..8db967f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "automatic-comment-blocks", "displayName": "Automatic Comment Blocks", "description": "Provides block comment completion for Javadoc-style multi-line comments and single-line comment blocks for most officially supported languages.", - "version": "1.1.9", + "version": "1.1.10", "publisher": "ycodetech", "homepage": "https://github.com/ycodetech/auto-comment-blocks", "repository": { From 1135d572131b11c7450db38ea002bab4fe1db921 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 07:28:53 +0100 Subject: [PATCH 46/48] docs: remove known issue about not working in WSL2. This is now fixed. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e04d1f6..239c60a 100644 --- a/README.md +++ b/README.md @@ -171,8 +171,6 @@ Reload the extension after changing any settings. - Currently, VS Code only allows extensions to overwrite, instead of modify, existing language configurations. This means that this extension may clash with another extension that overwrites the same language configurations, causing one or both not to work. In that case, uninstalling this extension is the only option for now. -- Doesn't work properly on Windows Linux WSL2. VScode API only finds language configs that are installed only on WSL2, and not also on Windows. That means all the normal built-in as well as 3rd party extensions won't have auto comment blocks support in WSL2. (Related issue: [#6](https://github.com/yCodeTech/auto-comment-blocks/issues/6)) - Please [report an issue](https://github.com/yCodeTech/auto-comment-blocks/issues/new) if you find any bugs, or have questions or feature requests. As of v1.1.7, debugging information is now logged to a dedicated `Auto Comment Blocks` Output channel. Please save the entire log to file using the `Save Output As` button in the Output's "3-dot menu", and attach the file to any new issue. From e179f182775c067333b8974ff14feb5b2e4d3c9e Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 16:23:35 +0100 Subject: [PATCH 47/48] chore(editorconfig): add auto indent style for `.md` to `space` --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index b0f9a34..aa9b6cc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,6 +13,7 @@ indent_size = 4 [*.md] trim_trailing_whitespace = false +indent_style = space [*.yml] indent_style = space From 8b7a08765458cef3aacd94a7b3b0776d0982a5dc Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 21 Jul 2025 22:13:55 +0100 Subject: [PATCH 48/48] docs: updated changelog --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b78b59..c1bd5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ All notable changes to this extension will be documented in this file. This Changelog uses the [Keep a Changelog](http://keepachangelog.com/) structure. +## [1.1.10](https://github.com/yCodeTech/auto-comment-blocks/releases/tag/v1.1.10) - 2025-07-21 + +#### Fixed: + +- Fixes yCodeTech/auto-comment-blocks#6 and indirectly yCodeTech/auto-comment-blocks#7 + + VS Code's `vscode.extensions.all` API doesn't find any built-in extensions on the Windows-side when running in WSL. It only gets the WSL-installed extensions, which causes the extension not to work at all because the language configuration file is not found. + + The workaround fix is to manually read the Windows extensions directories when running in WSL and merge them with the WSL-installed extensions. + +- Fixed language support detection to properly respect disabled language settings. + + Custom language configurations now correctly check if a language is disabled before applying support, preventing unwanted language activation. + +- Fixed support for languages with multiple extension configuration files by merging their configurations instead of overwriting them, ensuring complete language support. Also improved language configuration merging by properly handling comment configurations. + +#### Added: + +- Added the ability to get the all extensions directly from the directory on non-WSL systems (eg. Windows) because VS Code's `extensions.all` API only gets enabled extensions and doesn't include disabled ones, which could prevent language configs being found. + + - Removed `vscode.extensions.all` API call from `findAllLanguageConfigFilePaths` method in favour of getting the extensions directly from the directories. + +- Added new dependencies: + + - `is-wsl` for detecting WSL environments. + - `package-json-type` for TypeScript type definitions of package.json. + +- Added macOS keybinding support (`cmd+shift+m`) for the Blade override comments command. + +#### Changed: + +- Refactored extension architecture with improved separation of concerns. + + Major code reorganisation including extraction of utility functions, centralised extension data management, and improved debugging capabilities. + + - Added new `ExtensionData` class to centralize extension metadata management. + + This new class provides a clean interface for accessing extension details like ID, name, version, and various system paths, improving code organisation and maintainability. + + - Added new `utils.ts` file with shared utility functions. + + Extracted common functionality into reusable utility functions including JSON file operations, regex reconstruction, array merging, and data conversion utilities. + +- Updated debug logging to provide more comprehensive environment and configuration information. Enhanced diagnostic output now includes detailed extension paths for both WSL and native environments, making troubleshooting easier. + ## [1.1.9](https://github.com/yCodeTech/auto-comment-blocks/releases/tag/v1.1.9) - 2025-07-12 #### Fixed: