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 91922b1..3f1679e 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" } ] @@ -108,6 +109,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 8c643e1..de1cdf2 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"; @@ -21,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. */ @@ -60,25 +66,12 @@ export class Configuration { public constructor(logger: Logger) { this.logger = logger; - // 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})`); + this.setExtensionData(); - 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()); + // 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(); this.setLanguageConfigDefinitions(); @@ -87,11 +80,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(); } /** @@ -212,22 +201,76 @@ 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; + + // 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); + } } /** @@ -236,7 +279,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); } /** @@ -322,8 +365,28 @@ export class Configuration { * (built-in and 3rd party). */ private findAllLanguageConfigFilePaths() { + const extensions: any[] = []; + + // 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"); + + // Read the paths and create arrays of the extensions. + 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); + } + + // 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, @@ -443,6 +506,46 @@ 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()) { + // 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"); + + // 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. * @@ -519,16 +622,24 @@ export class Configuration { // If the config object has own property of comments AND the comments key has // own property of lineComment... if (Object.hasOwn(config, "comments") && Object.hasOwn(config.comments, "lineComment")) { + let lineComment = config.comments.lineComment; + + // Line comments can be a string or an object with a "comment" key. + // If the lineComment is an object, get the "comment" key value. + if (Object.hasOwn(lineComment, "comment")) { + lineComment = lineComment.comment; + } + // If the lineComment is "//"... - if (config.comments.lineComment === "//") { + if (lineComment === "//") { style = "//"; } // If the lineComment is "#"... - else if (config.comments.lineComment === "#") { + else if (lineComment === "#") { style = "#"; } // If the lineComment includes a ";" (; or ;;)... - else if (config.comments.lineComment.includes(";")) { + else if (lineComment.includes(";")) { style = ";"; } @@ -961,7 +1072,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)) { @@ -985,7 +1096,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" ); @@ -993,4 +1104,55 @@ export class Configuration { this.setBladeComments(false); } } + + /** + * 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 = this.getExtensionData("builtInExtensionsPath"); + + let extensionsPaths = {}; + + if (isWsl) { + // 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": windowsBuiltInExtensionsPath, + "Windows-installed User Extensions Path": windowsUserExtensionsPath, + "WSL-installed Built-in Extensions Path": builtInExtensionsPath, + "WSL-installed User Extensions Path": this.getExtensionData("userExtensionsPath"), + }; + } else { + extensionsPaths = { + "Built-in Extensions Path": builtInExtensionsPath, + "User Extensions Path": this.getExtensionData("userExtensionsPath"), + }; + } + + const env = { + "OS": process.platform, + "Platform": process.platform, + "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); + + // 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)); + } } 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");