From 27887bedef05bb145980e5b6ea73fe6b2d5ed4e0 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 29 Sep 2022 20:36:02 +0200 Subject: [PATCH] #412 - splitting configuration files --- CHANGELOG.md | 2 + src/commands/Dashboard.ts | 2 +- src/commands/Diagnostics.ts | 7 ++ src/explorerView/ExplorerView.ts | 2 +- src/extension.ts | 4 +- src/helpers/SettingsHelper.ts | 111 ++++++++++++++++++++++++++----- 6 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd34d34..c94a2eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### ✨ New features +- [#412](https://github.com/estruyf/vscode-front-matter/issues/412): Allow `frontmatter.json` to be split in multiple files + ### 🎨 Enhancements - [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index e0d888b9..b99c44ee 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -130,7 +130,7 @@ export class Dashboard { await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false); }); - SettingsHelper.onConfigChange((global?: any) => { + SettingsHelper.onConfigChange(() => { SettingsListener.getSettings(); }); diff --git a/src/commands/Diagnostics.ts b/src/commands/Diagnostics.ts index 23974cfb..fc24be1d 100644 --- a/src/commands/Diagnostics.ts +++ b/src/commands/Diagnostics.ts @@ -3,6 +3,7 @@ import { ViewColumn, workspace } from "vscode"; import ContentProvider from "../providers/ContentProvider"; import { join } from "path"; import { ContentFolder } from "../models"; +import { Settings } from "../helpers/SettingsHelper"; export class Diagnostics { @@ -38,6 +39,12 @@ ${all} # Folders to search files ${folderData.join("\n")} + +# Complete frontmatter.json config + +\`\`\`json +${JSON.stringify(Settings.globalConfig, null, 2)} +\`\`\` `; ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One); diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index a61bd54a..58966fdb 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -100,7 +100,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } }, this); - Settings.onConfigChange((global?: any) => { + Settings.onConfigChange(() => { SettingsListener.getSettings(); }); } diff --git a/src/extension.ts b/src/extension.ts index aa5317a3..c463e958 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -41,7 +41,7 @@ export async function activate(context: vscode.ExtensionContext) { return undefined; } - SettingsHelper.init(); + await SettingsHelper.init(); extension.migrateSettings(); SettingsHelper.checkToPromote(); @@ -201,7 +201,7 @@ export async function activate(context: vscode.ExtensionContext) { }); // Things to do when configuration changes - SettingsHelper.onConfigChange((global?: any) => { + SettingsHelper.onConfigChange(() => { Preview.init(); GitListener.init(); diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 9f711b66..5fbac08e 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -4,10 +4,10 @@ import { Notifications } from './Notifications'; import { commands, Uri, workspace, window } from 'vscode'; import * as vscode from 'vscode'; import { ContentType, CustomTaxonomy, TaxonomyType } from '../models'; -import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME } from '../constants'; +import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_SNIPPETS, SETTING_CONTENT_PLACEHOLDERS } from '../constants'; import { Folders } from '../commands/Folders'; -import { join, basename } from 'path'; -import { existsSync, readFileSync, watch, writeFileSync } from 'fs'; +import { join, basename, dirname, parse } from 'path'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; import { Extension } from './Extension'; import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; @@ -15,14 +15,16 @@ import * as jsoncParser from 'jsonc-parser'; export class Settings { public static globalFile = "frontmatter.json"; + public static globalConfigFolder = ".frontmatter/config"; + public static globalConfig: any; private static config: vscode.WorkspaceConfiguration; - private static globalConfig: any; private static isInitialized: boolean = false; private static listeners: any[] = []; private static fileCreationWatcher: vscode.FileSystemWatcher | undefined; + private static readConfigPromise: Promise | undefined = undefined; - public static init() { - Settings.readConfig(); + public static async init() { + await Settings.readConfig(); Settings.listeners = []; @@ -34,8 +36,7 @@ export class Settings { Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); - Settings.onConfigChange((global?: any) => { - Settings.readConfig(); + Settings.onConfigChange(async () => { Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); }); } @@ -97,19 +98,26 @@ export class Settings { if (Settings.checkProjectConfig(filename)) { Logger.info(`Config change detected - ${projectConfig} saved`); - const file = await workspace.openTextDocument(e.uri); - if (file) { - const fileContents = file.getText(); - const json = jsoncParser.parse(fileContents); - configDebouncer(() => callback(json), 200); - // callback(json) + Logger.info(`Reloading config...`); + if (Settings.readConfigPromise === undefined) { + Settings.readConfigPromise = Settings.readConfig(); } + await Settings.readConfigPromise; + + Logger.info(`Reloaded config...`); + configDebouncer(() => callback(), 200); } }); - workspace.onDidDeleteFiles((e) => { + workspace.onDidDeleteFiles(async (e) => { const needCallback = e?.files.find(f => Settings.checkProjectConfig(f.fsPath)); if (needCallback) { + Logger.info(`Reloading config...`); + if (Settings.readConfigPromise === undefined) { + Settings.readConfigPromise = Settings.readConfig(); + } + await Settings.readConfigPromise; + callback(); } }); @@ -391,7 +399,11 @@ export class Settings { */ private static checkProjectConfig(filePath: string) { const fmConfig = Settings.projectConfigPath; - if (fmConfig && existsSync(fmConfig)) { + filePath = parseWinPath(filePath); + + if (filePath.includes(Settings.globalConfigFolder)) { + return true; + } else if (fmConfig && existsSync(fmConfig)) { return filePath && basename(filePath).toLowerCase() === Settings.globalFile.toLowerCase() && fmConfig.toLowerCase() === filePath.toLowerCase(); @@ -403,7 +415,7 @@ export class Settings { /** * Read the global config file */ - private static readConfig() { + private static async readConfig() { try { const fmConfig = Settings.projectConfigPath; if (fmConfig && existsSync(fmConfig)) { @@ -413,11 +425,76 @@ export class Settings { } else { Settings.globalConfig = undefined; } + + // Read the files from the config folder + let configFiles = await workspace.findFiles(`**/${Settings.globalConfigFolder}/**`); + if (configFiles.length === 0) { + Logger.info(`No ".frontmatter/config" config files found.`); + } + + // Sort the files by fsPath + configFiles = configFiles.sort((a, b) => a.fsPath.localeCompare(b.fsPath)); + + for await (const configFile of configFiles) { + await Settings.processConfigFile(configFile); + } } catch (e) { Settings.globalConfig = undefined; Notifications.error(`Error reading "frontmatter.json" config file. Check [output window](command:${COMMAND_NAME.showOutputChannel}) for more details.`); Logger.error((e as Error).message); } + + Settings.readConfigPromise = undefined; + } + + /** + * Process the config file + * @param configFile + * @returns + */ + private static async processConfigFile(configFile: Uri) { + try { + const config = await workspace.fs.readFile(configFile); + const configJson = jsoncParser.parse(config.toString()); + + const filePath = parseWinPath(configFile.fsPath); + const configFilePath = filePath.split(Settings.globalConfigFolder).pop(); + if (!configFilePath) { + return; + } + Logger.info(`Processing "${configFilePath}" config file.`); + + // Get the path without the filename + const configFolder = parseWinPath(dirname(configFilePath)); + let relSettingName = configFolder.split('/').join('.'); + if (relSettingName.startsWith('.')) { + relSettingName = relSettingName.substring(1); + } + + const settingName = `frontMatter${relSettingName.startsWith('.') ? '' : '.'}${relSettingName}`; + + if (!Settings.globalConfig) { + Settings.globalConfig = {}; + } + + // Array settings + if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES || + relSettingName === SETTING_CONTENT_PAGE_FOLDERS || + relSettingName === SETTING_CONTENT_PLACEHOLDERS) { + const crntValue = Settings.globalConfig[settingName] || []; + Settings.globalConfig[settingName] = [...crntValue, configJson]; + } + // Object settings + else if (relSettingName === SETTING_CONTENT_SNIPPETS) { + // Filename is the key + const fileName = parse(configFilePath).name; + const crntValue = Settings.globalConfig[settingName] || {}; + Settings.globalConfig[settingName] = { ...crntValue, ...{ [fileName]: configJson } }; + } + } catch (e) { + Logger.error(`Error reading config file: ${configFile.fsPath}`); + Logger.error((e as Error).message); + } } /**