Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 73 additions & 29 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,32 @@ export class Configuration {
*/
private readonly multiLineLangDefinitionFilePath: string = `${this.autoGeneratedDir}/multi-line-languages.json`;

/**
* The default multi-line configuration.
*/
private readonly defaultMultiLineConfig: vscode.LanguageConfiguration;

/**
* The languages to skip, like plaintext, that don't have comment syntax.
*/
private readonly languagesToSkip: JsonObject;

/***********
* Methods *
***********/

public constructor() {
const configPath = `${__dirname}/../../config`;
// Read the default multi-line config from the JSON file and cache it for later use.
this.defaultMultiLineConfig = utils.readJsonFile(`${configPath}/default-multi-line-config.json`) as vscode.LanguageConfiguration;
// Read the languages to skip from the JSON file and cache it for later use.
this.languagesToSkip = utils.readJsonFile(`${configPath}/skip-languages.jsonc`);

this.findAllLanguageConfigFilePaths();
this.setLanguageConfigDefinitions();

this.setMultiLineCommentLanguageDefinitions();
this.setSingleLineCommentLanguageDefinitions();
this.writeCommentLanguageDefinitionsToJsonFile();

this.logDebugInfo();
}
Expand Down Expand Up @@ -300,8 +315,7 @@ export class Configuration {
* @returns {JsonArray}
*/
private getLanguagesToSkip(): JsonArray {
const json = utils.readJsonFile(`${__dirname}/../../config/skip-languages.jsonc`);
return json.languages as JsonArray;
return this.languagesToSkip.languages as JsonArray;
}

/**
Expand Down Expand Up @@ -662,24 +676,53 @@ export class Configuration {
this.singleLineBlocksMap.set("customSupportedLanguages", new Map([...tempMap].sort()));
}

/**
* Get the single-line language definitions object
* formatted and reversed by comment style for
* logging and development debug file output.
*
* @returns {SingleLineLanguageDefinitions}
*/
public getSingleLineLanguageDefinitions(): SingleLineLanguageDefinitions {
return utils.convertMapToReversedObject<SingleLineLanguageDefinitions>(this.singleLineBlocksMap);
}

/**
* Get the multi-line language definitions object
* formatted for logging and development debug file output.
*
* @returns {MultiLineLanguageDefinitions}
*/
public getMultiLineLanguageDefinitions(): MultiLineLanguageDefinitions {
return Object.fromEntries(this.multiLineBlocksMap) as unknown as MultiLineLanguageDefinitions;
}

/**
* Write Comment Language Definitions to the respective JSON file:
* either multi-line-languages.json, or single-line-languages.json.
*/
private writeCommentLanguageDefinitionsToJsonFile() {
public writeCommentLanguageDefinitionsToJsonFile() {
// Ensure the auto-generated directory exists.
utils.ensureDirExists(this.autoGeneratedDir);

// Convert the singleLineBlocksMap to an object.
const singleLineData = utils.convertMapToReversedObject<SingleLineLanguageDefinitions>(this.singleLineBlocksMap);

const multiLineData = Object.fromEntries(this.multiLineBlocksMap) as unknown as MultiLineLanguageDefinitions;

// Write into the single-line-languages.json file.
utils.writeJsonFile(this.singleLineLangDefinitionFilePath, singleLineData);
utils.writeJsonFile(this.singleLineLangDefinitionFilePath, this.getSingleLineLanguageDefinitions());

// Write into the multi-line-languages.json file.
utils.writeJsonFile(this.multiLineLangDefinitionFilePath, multiLineData);
utils.writeJsonFile(this.multiLineLangDefinitionFilePath, this.getMultiLineLanguageDefinitions());
}

/**
* Update language definitions.
*/
public updateLanguageDefinitions() {
// Remove all elements from the current Map, so we can update
// the definitions with an empty Map.
this.singleLineBlocksMap.clear();
this.multiLineBlocksMap.clear();
// Update the definitions.
this.setSingleLineCommentLanguageDefinitions();
this.setMultiLineCommentLanguageDefinitions();
}

/**
Expand All @@ -693,7 +736,7 @@ export class Configuration {
*
* This method performs the following tasks:
* - Retrieves the internal language configuration for the specified language ID.
* - Reads the default multi-line configuration from a JSON file.
* - Uses the cached default multi-line configuration.
* - Merges the default multi-line configuration with the internal language configuration if
* multiLine is `true`.
* - Sets the appropriate comment styles and onEnter rules.
Expand All @@ -705,14 +748,17 @@ export class Configuration {
* with rogue characters being inserted on new lines.
*/
private setLanguageConfiguration(langId: LanguageId, multiLine?: boolean, singleLineStyle?: SingleLineCommentStyle): vscode.Disposable {
const internalLangConfig: vscode.LanguageConfiguration = this.getLanguageConfig(langId);
const defaultMultiLineConfig = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`) as vscode.LanguageConfiguration;
const internalLangConfig: vscode.LanguageConfiguration | undefined = this.getLanguageConfig(langId);

let langConfig = {...internalLangConfig};
// Deep-clone the internalLangConfig so modifications never write back
// into the cached `languageConfigs` Map by accident.
let langConfig: vscode.LanguageConfiguration = internalLangConfig
? structuredClone(internalLangConfig)
: {};

if (multiLine) {
langConfig.autoClosingPairs = utils.mergeArraysBy<vscode.AutoClosingPair>(
defaultMultiLineConfig.autoClosingPairs,
this.defaultMultiLineConfig.autoClosingPairs,
internalLangConfig?.autoClosingPairs,
"open"
);
Expand All @@ -725,13 +771,17 @@ export class Configuration {
);

// Only assign the default config comments if it doesn't already exist.
// (nullish assignment operator ??=)
langConfig.comments ??= defaultMultiLineConfig.comments;
// Clone the comments to avoid modifying the original object down the line.
langConfig.comments ??= structuredClone(this.defaultMultiLineConfig.comments);

// If the default multi-line comments has been overridden for the langId,
// add the overridden multi-line comments to the langConfig.
if (this.isLangIdMultiLineCommentOverridden(langId)) {
langConfig.comments.blockComment[0] = this.getOverriddenMultiLineComment(langId);
// AND the langConfig has a comments key with a blockComment key, then
// update the opening comment style while preserving the ending.
if (this.isLangIdMultiLineCommentOverridden(langId) && langConfig.comments?.blockComment) {
langConfig.comments.blockComment = [
this.getOverriddenMultiLineComment(langId),
langConfig.comments.blockComment[1]
];
}

/**
Expand Down Expand Up @@ -1017,16 +1067,10 @@ export class Configuration {
logger.debug("The language configs found are:", this.languageConfigs);

// Multi-line language definitions.
logger.debug(
"The supported languages for multi-line blocks:",
utils.readJsonFile<MultiLineLanguageDefinitions>(this.multiLineLangDefinitionFilePath)
);
logger.debug("The supported languages for multi-line blocks:", this.getMultiLineLanguageDefinitions());

// Single-line language definitions.
logger.debug(
"The supported languages for single-line blocks:",
utils.readJsonFile<SingleLineLanguageDefinitions>(this.singleLineLangDefinitionFilePath)
);
logger.debug("The supported languages for single-line blocks:", this.getSingleLineLanguageDefinitions());
}

/**
Expand Down
57 changes: 31 additions & 26 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ export function activate(context: vscode.ExtensionContext) {
const extensionName = extensionData.get("namespace");
const extensionDisplayName = extensionData.get("displayName");

// In development mode, write language definitions to JSON files for debugging/reference.
if (context.extensionMode !== vscode.ExtensionMode.Production) {
configuration.writeCommentLanguageDefinitionsToJsonFile();
}

// Store disposables for cleanup
const disposables: vscode.Disposable[] = [];
let commentBlocksDisposables: vscode.Disposable[] = [];
Expand Down Expand Up @@ -75,21 +80,39 @@ export function activate(context: vscode.ExtensionContext) {
logger.setLogLevel(logLevel);
}

// Settings that require an extension host reload when changed.
const reloadRequiredSettings = [
"disabledLanguages",
"overrideDefaultLanguageMultiLineComments",
/**
* Automatically update (without extension host reload) language definitions and
* reconfigure the comment blocks when any of the following settings are changed.
*/
const languageSettings = [
"multiLineStyleBlocks",
"slashStyleBlocks",
"hashStyleBlocks",
"semicolonStyleBlocks",
"disabledLanguages",
"overrideDefaultLanguageMultiLineComments",
];

// Settings that require extension host reload
for (const setting of reloadRequiredSettings) {
for (const setting of languageSettings) {
if (event.affectsConfiguration(`${extensionName}.${setting}`)) {
showReloadMessage(extensionName, setting);
break; // Only show one reload message at a time
logger.info(`Configuration setting ${extensionName}.${setting} has changed.`);
// Dispose of old comment block configurations to prevent memory leaks
commentBlocksDisposables.forEach((disposable) => disposable.dispose());
commentBlocksDisposables = [];

configuration.updateLanguageDefinitions();

// In development mode, write updated language definitions to JSON files.
if (context.extensionMode !== vscode.ExtensionMode.Production) {
configuration.writeCommentLanguageDefinitionsToJsonFile();
}

commentBlocksDisposables = configuration.configureCommentBlocks();
disposables.push(...commentBlocksDisposables);

logger.info("Comment block configurations have been updated.");

break; // Only update once per change
}
}
});
Expand Down Expand Up @@ -132,21 +155,3 @@ export function activate(context: vscode.ExtensionContext) {
export function deactivate() {
logger.disposeLogger();
}

/**
* Shows a message prompting the user to reload the extension host.
* @param extensionName The namespace of the extension
* @param settingName The name of the setting that was changed
*/
function showReloadMessage(extensionName: string, settingName: string): void {
vscode.window
.showInformationMessage(
`The ${extensionName}.${settingName} setting has been changed. Please reload the Extension Host to take effect.`,
"Reload"
)
.then((selection) => {
if (selection === "Reload") {
vscode.commands.executeCommand("workbench.action.restartExtensionHost");
}
});
}
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
"rootDir": ".",
"typeRoots": ["./node_modules/@types"]
},
"typeAcquisition": {
"enable": true,
},
"exclude": ["node_modules", ".vscode-test"]
}
Loading