Add portable Zed Gist sync configuration
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDirectory = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
async function exclusionRules() {
|
||||
const rulesPath = join(scriptDirectory, "file-exclusions.json");
|
||||
const rules = JSON.parse(await readFile(rulesPath, "utf8"));
|
||||
if (!Array.isArray(rules.defaults) || !Array.isArray(rules.custom)) {
|
||||
throw new Error("file-exclusions.json must contain defaults and custom arrays");
|
||||
}
|
||||
return {
|
||||
defaults: [...new Set(rules.defaults)],
|
||||
custom: [...new Set(rules.custom)].filter(
|
||||
(entry) => !rules.defaults.includes(entry),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function settingsPath() {
|
||||
if (process.platform === "win32") {
|
||||
const appData = process.env.APPDATA;
|
||||
if (!appData) throw new Error("APPDATA is not set");
|
||||
return join(appData, "Zed", "settings.json");
|
||||
}
|
||||
return join(homedir(), ".config", "zed", "settings.json");
|
||||
}
|
||||
|
||||
function findArrayRange(source) {
|
||||
const property = /"file_scan_exclusions"\s*:/g.exec(source);
|
||||
if (!property) throw new Error("file_scan_exclusions is missing from settings.json");
|
||||
|
||||
const start = source.indexOf("[", property.index + property[0].length);
|
||||
if (start === -1) throw new Error("file_scan_exclusions is not an array");
|
||||
|
||||
let inString = false;
|
||||
let escaped = false;
|
||||
let depth = 0;
|
||||
for (let index = start; index < source.length; index += 1) {
|
||||
const character = source[index];
|
||||
if (inString) {
|
||||
if (escaped) escaped = false;
|
||||
else if (character === "\\") escaped = true;
|
||||
else if (character === '"') inString = false;
|
||||
continue;
|
||||
}
|
||||
if (character === '"') inString = true;
|
||||
else if (character === "[") depth += 1;
|
||||
else if (character === "]" && --depth === 0) return { start, end: index + 1 };
|
||||
}
|
||||
throw new Error("file_scan_exclusions array is not closed");
|
||||
}
|
||||
|
||||
function formatArray(values, indent, newline) {
|
||||
const itemIndent = `${indent} `;
|
||||
const items = values.map((value) => `${itemIndent}${JSON.stringify(value)}`);
|
||||
return `[${newline}${items.join(`,${newline}`)}${newline}${indent}]`;
|
||||
}
|
||||
|
||||
async function replaceAtomically(path, contents) {
|
||||
const temporaryPath = join(dirname(path), `.settings.json.${process.pid}.tmp`);
|
||||
try {
|
||||
await writeFile(temporaryPath, contents, "utf8");
|
||||
await rename(temporaryPath, path);
|
||||
} finally {
|
||||
await rm(temporaryPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
const path = settingsPath();
|
||||
const { defaults, custom } = await exclusionRules();
|
||||
const source = await readFile(path, "utf8");
|
||||
const newline = source.includes("\r\n") ? "\r\n" : "\n";
|
||||
const range = findArrayRange(source);
|
||||
const current = JSON.parse(source.slice(range.start, range.end));
|
||||
const customIsActive = current.some((entry) => !defaults.includes(entry));
|
||||
const next = customIsActive ? defaults : [...defaults, ...custom];
|
||||
const lineStart = source.lastIndexOf("\n", range.start) + 1;
|
||||
const indent = source.slice(lineStart, range.start).match(/^\s*/)?.[0] ?? "";
|
||||
const updated =
|
||||
source.slice(0, range.start) +
|
||||
formatArray(next, indent, newline) +
|
||||
source.slice(range.end);
|
||||
|
||||
await replaceAtomically(path, updated);
|
||||
console.log(`Excluded files are now ${customIsActive ? "shown" : "hidden"}.`);
|
||||
Reference in New Issue
Block a user