9 lines
39 KiB
JSON
9 lines
39 KiB
JSON
{
|
|
"keymap.json": "[\n {\n \"bindings\": {\n \"f12\": [\"zed::IncreaseUiFontSize\", { \"persist\": false }],\n \"secondary-f12\": [\"zed::DecreaseUiFontSize\", { \"persist\": false }],\n \"secondary-1\": \"project_panel::ToggleFocus\",\n \"secondary-2\": \"terminal_panel::ToggleFocus\",\n \"secondary-3\": \"workspace::ToggleRightDock\",\n \"secondary-shift-g\": \"git_panel::ToggleFocus\",\n \"secondary-shift-v\": \"zed::Extensions\",\n \"secondary-p\": \"command_palette::Toggle\",\n \"secondary-o\": \"file_finder::Toggle\",\n \"secondary-9\": \"pane::ActivatePreviousItem\",\n \"secondary-0\": \"pane::ActivateNextItem\",\n \"alt-1\": [\"workspace::ActivatePane\", 0],\n \"alt-2\": [\"workspace::ActivatePane\", 1],\n \"alt-3\": [\"workspace::ActivatePane\", 2],\n \"alt-4\": [\"workspace::ActivatePane\", 3],\n \"alt-5\": [\"workspace::ActivatePane\", 4],\n \"secondary-shift-o\": \"workspace::Open\",\n \"secondary-enter\": \"workspace::ToggleZoom\",\n \"secondary-r\": \"task::Spawn\",\n \"secondary-shift-r\": \"projects::OpenRecent\",\n \"shift-alt-r\": null,\n \"secondary-; k\": \"workspace::Reload\",\n \"secondary-; g\": \"git::FileHistory\",\n \"secondary-; p\": \"git::Push\",\n \"secondary-; secondary-p\": \"git::Pull\",\n \"secondary-; n\": \"git::Branch\",\n \"secondary-; s\": \"git::StashAll\",\n \"secondary-; z\": \"git::StashPop\",\n \"secondary-; a\": \"git::StageAll\",\n \"secondary-; u\": [\n \"task::Spawn\",\n { \"task_name\": \"Zed Settings: Push to GitHub Gist\" }\n ],\n \"secondary-; d\": [\n \"task::Spawn\",\n { \"task_name\": \"Zed Settings: Pull from GitHub Gist\" }\n ],\n \"shift-alt--\": [\n \"task::Spawn\",\n { \"task_name\": \"Toggle excluded files\" }\n ]\n }\n },\n {\n \"context\": \"Workspace\",\n \"bindings\": {\n \"secondary-; c\": \"git_panel::ToggleFocus\"\n }\n },\n {\n \"context\": \"GitPanel\",\n \"bindings\": {\n \"secondary-; c\": \"git_panel::FocusEditor\"\n }\n },\n {\n \"context\": \"Editor && mode == full\",\n \"bindings\": {\n \"secondary-shift-[\": \"editor::Fold\",\n \"secondary-shift-]\": \"editor::UnfoldLines\",\n \"secondary--\": \"editor::FoldAll\",\n \"secondary-=\": \"editor::UnfoldAll\"\n }\n },\n {\n \"context\": \"Editor && extension == md\",\n \"bindings\": {\n \"alt-p\": \"markdown::OpenPreview\",\n \"shift-alt-p\": \"markdown::OpenPreviewToTheSide\"\n }\n },\n {\n \"context\": \"ProjectPanel\",\n \"bindings\": {\n \"alt--\": \"project_panel::CollapseAllEntries\"\n }\n }\n]\n",
|
|
"scripts/file-exclusions.json": "{\n \"defaults\": [\n \"**/.git\",\n \"**/.svn\",\n \"**/.hg\",\n \"**/.jj\",\n \"**/CVS\",\n \"**/.DS_Store\",\n \"**/Thumbs.db\",\n \"**/.classpath\",\n \"**/.settings\"\n ],\n \"custom\": [\n \"**/.playwright-cli\",\n \"**/output\",\n \"**/.cursor\",\n \"**/.vscode\",\n \"**/.idea\",\n \"**/.fleet\",\n \"**/node_modules\",\n \"**/bun.lockb\",\n \"**/pnpm-lock.yaml\",\n \"**/package-lock.json\",\n \"**/yarn.lock\",\n \"**/dist\",\n \"**/out\",\n \"**/build\",\n \"**/.next\",\n \"**/.nuxt\",\n \"**/.output\",\n \"**/.cache\",\n \"**/.turbo\",\n \"**/.vercel\",\n \"**/.netlify\",\n \"**/tsconfig.tsbuildinfo\",\n \"**/auto-imports.d.ts\",\n \"**/components.json\",\n \"**/unplugin-vue-components.d.ts\",\n \"src/*.d.ts\",\n \"**/__pycache__\",\n \"**/__init__.py\",\n \"**/.ipynb_checkpoints\",\n \"**/.pytest_cache\",\n \"**/.mypy_cache\",\n \"**/.venv\",\n \"**/env\",\n \"**/venv\",\n \"**/.github\",\n \"**/.husky\",\n \"**/.npmrc\",\n \"**/.eslintrc*\",\n \"**/.prettier*\",\n \"**/.editorconfig\",\n \"**/.dockerignore\",\n \"**/docker-compose.override.yml\",\n \"**/cypress*\",\n \"**/prisma/migrations\"\n ]\n}\n",
|
|
"scripts/toggle-file-scan-exclusions.mjs": "import { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\n\nconst scriptDirectory = dirname(fileURLToPath(import.meta.url));\n\nasync function exclusionRules() {\n const rulesPath = join(scriptDirectory, \"file-exclusions.json\");\n const rules = JSON.parse(await readFile(rulesPath, \"utf8\"));\n if (!Array.isArray(rules.defaults) || !Array.isArray(rules.custom)) {\n throw new Error(\"file-exclusions.json must contain defaults and custom arrays\");\n }\n return {\n defaults: [...new Set(rules.defaults)],\n custom: [...new Set(rules.custom)].filter(\n (entry) => !rules.defaults.includes(entry),\n ),\n };\n}\n\nfunction settingsPath() {\n if (process.platform === \"win32\") {\n const appData = process.env.APPDATA;\n if (!appData) throw new Error(\"APPDATA is not set\");\n return join(appData, \"Zed\", \"settings.json\");\n }\n return join(homedir(), \".config\", \"zed\", \"settings.json\");\n}\n\nfunction findArrayRange(source) {\n const property = /\"file_scan_exclusions\"\\s*:/g.exec(source);\n if (!property) throw new Error(\"file_scan_exclusions is missing from settings.json\");\n\n const start = source.indexOf(\"[\", property.index + property[0].length);\n if (start === -1) throw new Error(\"file_scan_exclusions is not an array\");\n\n let inString = false;\n let escaped = false;\n let depth = 0;\n for (let index = start; index < source.length; index += 1) {\n const character = source[index];\n if (inString) {\n if (escaped) escaped = false;\n else if (character === \"\\\\\") escaped = true;\n else if (character === '\"') inString = false;\n continue;\n }\n if (character === '\"') inString = true;\n else if (character === \"[\") depth += 1;\n else if (character === \"]\" && --depth === 0) return { start, end: index + 1 };\n }\n throw new Error(\"file_scan_exclusions array is not closed\");\n}\n\nfunction formatArray(values, indent, newline) {\n const itemIndent = `${indent} `;\n const items = values.map((value) => `${itemIndent}${JSON.stringify(value)}`);\n return `[${newline}${items.join(`,${newline}`)}${newline}${indent}]`;\n}\n\nasync function replaceAtomically(path, contents) {\n const temporaryPath = join(dirname(path), `.settings.json.${process.pid}.tmp`);\n try {\n await writeFile(temporaryPath, contents, \"utf8\");\n await rename(temporaryPath, path);\n } finally {\n await rm(temporaryPath, { force: true });\n }\n}\n\nconst path = settingsPath();\nconst { defaults, custom } = await exclusionRules();\nconst source = await readFile(path, \"utf8\");\nconst newline = source.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\nconst range = findArrayRange(source);\nconst current = JSON.parse(source.slice(range.start, range.end));\nconst customIsActive = current.some((entry) => !defaults.includes(entry));\nconst next = customIsActive ? defaults : [...defaults, ...custom];\nconst lineStart = source.lastIndexOf(\"\\n\", range.start) + 1;\nconst indent = source.slice(lineStart, range.start).match(/^\\s*/)?.[0] ?? \"\";\nconst updated =\n source.slice(0, range.start) +\n formatArray(next, indent, newline) +\n source.slice(range.end);\n\nawait replaceAtomically(path, updated);\nconsole.log(`Excluded files are now ${customIsActive ? \"shown\" : \"hidden\"}.`);\n",
|
|
"scripts/zed-settings-sync.mjs": "import { createHash } from \"node:crypto\";\nimport { copyFile, mkdir, readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { spawnSync } from \"node:child_process\";\n\nconst BUNDLE_NAME = \"zed-settings-sync.json\";\nconst REQUIRED_FILES = [\"settings.json\", \"keymap.json\", \"tasks.json\"];\nconst FIXED_FILES = [\n ...REQUIRED_FILES,\n \"debug.json\",\n \"scripts/toggle-file-scan-exclusions.mjs\",\n \"scripts/file-exclusions.json\",\n \"scripts/zed-settings-sync.mjs\",\n];\nconst OPTIONAL_DIRECTORIES = [\"snippets\", \"themes\"];\nconst LOCAL_SYNC_CONFIG = \"scripts/settings-sync.json\";\n\nfunction zedConfigDirectory() {\n if (process.platform === \"win32\") {\n if (!process.env.APPDATA) throw new Error(\"APPDATA is not set\");\n return join(process.env.APPDATA, \"Zed\");\n }\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), \".config\"), \"zed\");\n}\n\nfunction timestamp() {\n return new Date().toISOString().replace(/[:.]/g, \"-\");\n}\n\nasync function writeAtomically(path, contents) {\n await mkdir(dirname(path), { recursive: true });\n const temporaryPath = join(dirname(path), `.${basename(path)}.${process.pid}.tmp`);\n try {\n await writeFile(temporaryPath, contents, \"utf8\");\n await rename(temporaryPath, path);\n } finally {\n await rm(temporaryPath, { force: true });\n }\n}\n\nasync function listFilesRecursively(directory, prefix) {\n if (!existsSync(directory)) return [];\n const results = [];\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n const absolutePath = join(directory, entry.name);\n const relativePath = `${prefix}/${entry.name}`;\n if (entry.isDirectory()) results.push(...await listFilesRecursively(absolutePath, relativePath));\n else if (entry.isFile()) results.push(relativePath);\n }\n return results;\n}\n\nasync function synchronizedPaths(configDirectory) {\n const paths = FIXED_FILES.filter((path) => existsSync(join(configDirectory, ...path.split(\"/\"))));\n for (const directory of OPTIONAL_DIRECTORIES) {\n paths.push(...await listFilesRecursively(join(configDirectory, directory), directory));\n }\n return [...new Set(paths)].sort();\n}\n\nfunction isAllowedBundlePath(path) {\n if (typeof path !== \"string\" || path.startsWith(\"/\") || path.includes(\"\\\\\")) return false;\n const segments = path.split(\"/\");\n if (segments.some((segment) => !segment || segment === \".\" || segment === \"..\")) return false;\n if (FIXED_FILES.includes(path) || path === LOCAL_SYNC_CONFIG) return true;\n return OPTIONAL_DIRECTORIES.some((directory) => path.startsWith(`${directory}/`));\n}\n\nfunction replaceTaskPath(source, path, placeholder) {\n return source\n .replaceAll(path.replaceAll(\"\\\\\", \"\\\\\\\\\"), placeholder)\n .replaceAll(path.replaceAll(\"\\\\\", \"/\"), placeholder);\n}\n\nfunction portableTasks(source, configDirectory) {\n return replaceTaskPath(\n replaceTaskPath(\n source,\n join(configDirectory, \"scripts\", \"toggle-file-scan-exclusions.mjs\"),\n \"__TOGGLE_SCRIPT__\",\n ),\n join(configDirectory, \"scripts\", \"zed-settings-sync.mjs\"),\n \"__SYNC_SCRIPT__\",\n );\n}\n\nfunction installedTasks(source, configDirectory) {\n return source\n .replaceAll(\n \"__TOGGLE_SCRIPT__\",\n join(configDirectory, \"scripts\", \"toggle-file-scan-exclusions.mjs\").replaceAll(\"\\\\\", \"/\"),\n )\n .replaceAll(\n \"__SYNC_SCRIPT__\",\n join(configDirectory, \"scripts\", \"zed-settings-sync.mjs\").replaceAll(\"\\\\\", \"/\"),\n );\n}\n\nfunction hashContents(contents) {\n return createHash(\"sha256\").update(contents).digest(\"hex\");\n}\n\nasync function createBundle() {\n const configDirectory = zedConfigDirectory();\n const files = {};\n for (const relativePath of await synchronizedPaths(configDirectory)) {\n const absolutePath = join(configDirectory, ...relativePath.split(\"/\"));\n let contents = await readFile(absolutePath, \"utf8\");\n if (relativePath === \"tasks.json\") contents = portableTasks(contents, configDirectory);\n files[relativePath] = contents;\n }\n for (const required of REQUIRED_FILES) {\n if (!(required in files)) throw new Error(`Cannot export: ${required} is missing`);\n }\n const contents = serializeBundle(files);\n return { files, contents, hash: hashContents(contents) };\n}\n\nfunction serializeBundle(files) {\n return `${JSON.stringify(files, null, 2)}\\n`;\n}\n\nfunction parseBundle(contents, sourceName) {\n let files;\n try {\n files = JSON.parse(contents);\n } catch (error) {\n throw new Error(`Invalid JSON in ${sourceName}: ${error.message}`);\n }\n if (!files || Array.isArray(files) || typeof files !== \"object\") {\n throw new Error(`Invalid Zed settings bundle: ${sourceName}`);\n }\n for (const required of REQUIRED_FILES) {\n if (typeof files[required] !== \"string\") {\n throw new Error(`Invalid bundle: ${required} is missing`);\n }\n }\n for (const [path, value] of Object.entries(files)) {\n if (!isAllowedBundlePath(path) || typeof value !== \"string\") {\n throw new Error(`Invalid bundle file: ${path}`);\n }\n }\n return { files, hash: hashContents(contents) };\n}\n\nasync function exportBundle(path) {\n const bundle = await createBundle();\n await writeAtomically(path, bundle.contents);\n console.log(`Exported Zed settings to ${path}`);\n console.log(`SHA-256: ${bundle.hash}`);\n return bundle;\n}\n\nasync function backupCurrentConfiguration(configDirectory) {\n const backupDirectory = join(configDirectory, \"backups\", `sync-import-${timestamp()}`);\n await mkdir(backupDirectory, { recursive: true });\n for (const relativePath of await synchronizedPaths(configDirectory)) {\n const source = join(configDirectory, ...relativePath.split(\"/\"));\n const destination = join(backupDirectory, ...relativePath.split(\"/\"));\n await mkdir(dirname(destination), { recursive: true });\n await copyFile(source, destination);\n }\n return backupDirectory;\n}\n\nasync function installBundleContents(contents, sourceName) {\n const parsed = parseBundle(contents, sourceName);\n const configDirectory = zedConfigDirectory();\n const backupDirectory = await backupCurrentConfiguration(configDirectory);\n for (const [relativePath, sourceContents] of Object.entries(parsed.files)) {\n // Gist identity and sync history belong to each machine and are never imported.\n if (relativePath === LOCAL_SYNC_CONFIG) continue;\n const destination = join(configDirectory, ...relativePath.split(\"/\"));\n const installedContents = relativePath === \"tasks.json\"\n ? installedTasks(sourceContents, configDirectory)\n : sourceContents;\n await writeAtomically(destination, installedContents);\n }\n console.log(`Imported Zed settings from ${sourceName}`);\n console.log(`Previous settings were backed up to ${backupDirectory}`);\n return { ...parsed, backupDirectory };\n}\n\nasync function importBundle(path) {\n return installBundleContents(await readFile(path, \"utf8\"), path);\n}\n\nfunction runPowerShell(command, extraEnv = {}) {\n const result = spawnSync(\"powershell.exe\", [\"-NoProfile\", \"-STA\", \"-Command\", command], {\n encoding: \"utf8\",\n env: { ...process.env, ...extraEnv },\n windowsHide: false,\n });\n if (result.status !== 0) throw new Error(result.stderr.trim() || \"Windows dialog failed\");\n return result.stdout.trim();\n}\n\nfunction nativeFileDialog(mode) {\n if (process.platform === \"win32\") {\n const common = \"[Console]::OutputEncoding=[Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); \";\n const command = mode === \"save\"\n ? `${common}$d=New-Object System.Windows.Forms.SaveFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; $d.AddExtension=$true; if($d.ShowDialog($o) -eq 'OK'){[Console]::Write($d.FileName)}; $o.Close()`\n : `${common}$d=New-Object System.Windows.Forms.OpenFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; if($d.ShowDialog($o) -eq 'OK'){[Console]::Write($d.FileName)}; $o.Close()`;\n return runPowerShell(command);\n }\n if (process.platform === \"darwin\") {\n const script = mode === \"save\"\n ? `POSIX path of (choose file name with prompt \"Export Zed settings\" default name \"${BUNDLE_NAME}\")`\n : \"POSIX path of (choose file with prompt \\\"Import Zed settings\\\")\";\n const result = spawnSync(\"osascript\", [\"-e\", script], { encoding: \"utf8\" });\n return result.status === 0 ? result.stdout.trim() : \"\";\n }\n const args = [\"--file-selection\", `--title=${mode === \"save\" ? \"Export\" : \"Import\"} Zed settings`];\n if (mode === \"save\") args.push(\"--save\", \"--confirm-overwrite\", `--filename=${BUNDLE_NAME}`);\n const result = spawnSync(\"zenity\", args, { encoding: \"utf8\" });\n return result.status === 0 ? result.stdout.trim() : \"\";\n}\n\nfunction nativeInput(prompt, title, defaultValue = \"\") {\n if (process.platform === \"win32\") {\n const command = `\n [Console]::OutputEncoding=[Text.Encoding]::UTF8\n Add-Type -AssemblyName System.Windows.Forms\n Add-Type -AssemblyName System.Drawing\n $f=New-Object System.Windows.Forms.Form\n $f.Text=$env:ZED_SYNC_TITLE\n $f.Size=New-Object System.Drawing.Size(620,190)\n $f.StartPosition='CenterScreen'\n $f.TopMost=$true\n $f.ShowInTaskbar=$true\n $f.FormBorderStyle='FixedDialog'\n $f.MaximizeBox=$false\n $f.MinimizeBox=$false\n $l=New-Object System.Windows.Forms.Label\n $l.Text=$env:ZED_SYNC_PROMPT\n $l.AutoSize=$false\n $l.Location=New-Object System.Drawing.Point(14,14)\n $l.Size=New-Object System.Drawing.Size(575,42)\n $t=New-Object System.Windows.Forms.TextBox\n $t.Text=$env:ZED_SYNC_DEFAULT\n $t.Location=New-Object System.Drawing.Point(14,62)\n $t.Size=New-Object System.Drawing.Size(575,24)\n $ok=New-Object System.Windows.Forms.Button\n $ok.Text='OK'\n $ok.DialogResult=[System.Windows.Forms.DialogResult]::OK\n $ok.Location=New-Object System.Drawing.Point(427,105)\n $cancel=New-Object System.Windows.Forms.Button\n $cancel.Text='Cancel'\n $cancel.DialogResult=[System.Windows.Forms.DialogResult]::Cancel\n $cancel.Location=New-Object System.Drawing.Point(514,105)\n $f.Controls.AddRange(@($l,$t,$ok,$cancel))\n $f.AcceptButton=$ok\n $f.CancelButton=$cancel\n $f.Add_Shown({$f.Activate();$t.Focus();$t.SelectAll()})\n if($f.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){[Console]::Write($t.Text)}\n `;\n return runPowerShell(command, {\n ZED_SYNC_PROMPT: prompt,\n ZED_SYNC_TITLE: title,\n ZED_SYNC_DEFAULT: defaultValue,\n });\n }\n if (process.platform === \"darwin\") {\n const escaped = (value) => value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll('\"', '\\\\\"');\n const script = `text returned of (display dialog \"${escaped(prompt)}\" with title \"${escaped(title)}\" default answer \"${escaped(defaultValue)}\")`;\n const result = spawnSync(\"osascript\", [\"-e\", script], { encoding: \"utf8\" });\n return result.status === 0 ? result.stdout.trim() : \"\";\n }\n const result = spawnSync(\"zenity\", [\"--entry\", `--title=${title}`, `--text=${prompt}`, `--entry-text=${defaultValue}`], { encoding: \"utf8\" });\n return result.status === 0 ? result.stdout.trim() : \"\";\n}\n\nfunction nativeMessage(message, title = \"Zed Settings Sync\", error = false) {\n if (process.platform === \"win32\") {\n const command = \"Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); [void][System.Windows.Forms.MessageBox]::Show($o,$env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'OK',$env:ZED_SYNC_ICON); $o.Close()\";\n runPowerShell(command, {\n ZED_SYNC_MESSAGE: message,\n ZED_SYNC_TITLE: title,\n ZED_SYNC_ICON: error ? \"Error\" : \"Information\",\n });\n return;\n }\n if (process.platform === \"darwin\") {\n const escaped = message.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll('\"', '\\\\\"');\n spawnSync(\"osascript\", [\"-e\", `display dialog \"${escaped}\" with title \"${title}\" buttons {\"OK\"}`]);\n return;\n }\n spawnSync(\"zenity\", [error ? \"--error\" : \"--info\", `--title=${title}`, `--text=${message}`]);\n}\n\nfunction nativeConfirm(message, title = \"Zed Settings Sync\") {\n if (process.platform === \"win32\") {\n const command = \"Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); [Console]::Write([System.Windows.Forms.MessageBox]::Show($o,$env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'YesNo','Warning')); $o.Close()\";\n return runPowerShell(command, { ZED_SYNC_MESSAGE: message, ZED_SYNC_TITLE: title }) === \"Yes\";\n }\n if (process.platform === \"darwin\") {\n const escaped = message.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll('\"', '\\\\\"');\n const result = spawnSync(\"osascript\", [\"-e\", `button returned of (display dialog \"${escaped}\" with title \"${title}\" buttons {\"Cancel\", \"Continue\"} default button \"Continue\" cancel button \"Cancel\")`], { encoding: \"utf8\" });\n return result.status === 0 && result.stdout.trim() === \"Continue\";\n }\n return spawnSync(\"zenity\", [\"--question\", `--title=${title}`, `--text=${message}`]).status === 0;\n}\n\nasync function readSyncConfiguration() {\n const path = join(zedConfigDirectory(), ...LOCAL_SYNC_CONFIG.split(\"/\"));\n let source = {};\n try {\n source = JSON.parse(await readFile(path, \"utf8\"));\n } catch (error) {\n if (error.code !== \"ENOENT\") throw error;\n }\n return {\n path,\n config: {\n gist_id: typeof source.gist_id === \"string\" ? source.gist_id : \"\",\n last_synced_hash: typeof source.last_synced_hash === \"string\" ? source.last_synced_hash : \"\",\n },\n };\n}\n\nasync function saveSyncConfiguration(path, config) {\n await writeAtomically(path, `${JSON.stringify(config, null, 2)}\\n`);\n}\n\nfunction normalizeGistId(value) {\n const matches = value.trim().match(/[a-f0-9]{8,}/gi);\n if (!matches?.length) throw new Error(\"Enter a valid GitHub Gist ID or URL\");\n return matches.at(-1).toLowerCase();\n}\n\nasync function promptForGist() {\n const state = await readSyncConfiguration();\n console.log(\"Opening Gist configuration dialog...\");\n const entered = nativeInput(\n \"Paste the GitHub Gist ID or full Gist URL. The previous value is remembered on this machine.\",\n \"Zed Settings Gist\",\n state.config.gist_id,\n );\n if (!entered) return null;\n const gistId = normalizeGistId(entered);\n if (gistId !== state.config.gist_id) {\n state.config.gist_id = gistId;\n state.config.last_synced_hash = \"\";\n await saveSyncConfiguration(state.path, state.config);\n }\n return state;\n}\n\nfunction runGh(args, input) {\n const env = { ...process.env };\n delete env.GH_TOKEN;\n delete env.GITHUB_TOKEN;\n const result = spawnSync(\"gh\", args, { encoding: \"utf8\", env, input, windowsHide: true });\n if (result.error?.code === \"ENOENT\") {\n throw new Error(\"GitHub CLI (gh) is not installed. Install it, then run: gh auth login\");\n }\n if (result.status !== 0) throw new Error(result.stderr.trim() || `gh ${args[0]} failed`);\n return result.stdout.trim();\n}\n\nfunction readRemoteGist(gistId) {\n const gist = JSON.parse(runGh([\"api\", `gists/${gistId}`]));\n const file = gist.files?.[BUNDLE_NAME];\n if (!file) return { gist, parsed: null, contents: null };\n if (file.truncated) throw new Error(`${BUNDLE_NAME} is too large to read through the Gist API`);\n return { gist, parsed: parseBundle(file.content, `Gist ${gistId}`), contents: file.content };\n}\n\nfunction updateRemoteGist(gistId, contents) {\n const payload = JSON.stringify({ files: { [BUNDLE_NAME]: { content: contents } } });\n return JSON.parse(runGh([\"api\", \"--method\", \"PATCH\", `gists/${gistId}`, \"--input\", \"-\"], payload));\n}\n\nfunction createRemoteGist(contents) {\n const payload = JSON.stringify({\n description: \"Zed settings sync\",\n public: false,\n files: { [BUNDLE_NAME]: { content: contents } },\n });\n return JSON.parse(runGh([\"api\", \"--method\", \"POST\", \"gists\", \"--input\", \"-\"], payload));\n}\n\nfunction shortHash(hash) {\n return hash ? hash.slice(0, 12) : \"none\";\n}\n\nasync function configureGist() {\n const state = await promptForGist();\n if (!state) return;\n nativeMessage(`Saved Gist ID:\\n${state.config.gist_id}\\n\\nAuthentication is managed by GitHub CLI (gh).`);\n}\n\nasync function pushGist() {\n const state = await readSyncConfiguration();\n const localBundle = await createBundle();\n if (!state.config.gist_id) {\n console.log(\"No Gist is configured. Asking to create one...\");\n const proceed = nativeConfirm(\n \"No GitHub Gist is configured. Create a new Secret Gist from this machine's current Zed settings?\",\n );\n if (!proceed) return;\n const created = createRemoteGist(localBundle.contents);\n state.config.gist_id = created.id;\n state.config.last_synced_hash = localBundle.hash;\n await saveSyncConfiguration(state.path, state.config);\n nativeMessage(`Created and uploaded Secret Gist:\\n${created.html_url || created.id}\\n\\nSHA-256: ${localBundle.hash}`);\n return;\n }\n const remote = readRemoteGist(state.config.gist_id);\n const remoteHash = remote.parsed?.hash || \"\";\n const lastHash = state.config.last_synced_hash;\n if (remoteHash && remoteHash !== localBundle.hash) {\n const remoteChanged = !lastHash || remoteHash !== lastHash;\n if (remoteChanged) {\n const proceed = nativeConfirm(\n `The Gist contains different settings.\\n\\nLocal: ${shortHash(localBundle.hash)}\\nRemote: ${shortHash(remoteHash)}\\nLast sync: ${shortHash(lastHash)}\\n\\nOverwrite the Gist with this machine's settings?`,\n );\n if (!proceed) return;\n }\n }\n const updated = updateRemoteGist(state.config.gist_id, localBundle.contents);\n state.config.last_synced_hash = localBundle.hash;\n await saveSyncConfiguration(state.path, state.config);\n nativeMessage(`Uploaded to Gist ${state.config.gist_id}\\n\\nSHA-256: ${localBundle.hash}\\nUpdated: ${updated.updated_at || \"unknown\"}`);\n}\n\nasync function pullGist() {\n const state = await promptForGist();\n if (!state) return;\n const localBundle = await createBundle();\n const remote = readRemoteGist(state.config.gist_id);\n if (!remote.parsed) throw new Error(`Gist ${state.config.gist_id} does not contain ${BUNDLE_NAME}`);\n if (localBundle.hash !== remote.parsed.hash) {\n const localChanged = !state.config.last_synced_hash || localBundle.hash !== state.config.last_synced_hash;\n if (localChanged) {\n const proceed = nativeConfirm(\n `This machine contains different settings.\\n\\nLocal: ${shortHash(localBundle.hash)}\\nRemote: ${shortHash(remote.parsed.hash)}\\nLast sync: ${shortHash(state.config.last_synced_hash)}\\n\\nReplace local settings with the Gist version? A backup will be created first.`,\n );\n if (!proceed) return;\n }\n await installBundleContents(remote.contents, `Gist ${state.config.gist_id}`);\n }\n state.config.last_synced_hash = remote.parsed.hash;\n await saveSyncConfiguration(state.path, state.config);\n nativeMessage(`Downloaded from Gist ${state.config.gist_id}\\n\\nSHA-256: ${remote.parsed.hash}\\nRemote updated: ${remote.gist.updated_at || \"unknown\"}`);\n}\n\nasync function showGistStatus() {\n const state = await promptForGist();\n if (!state) return;\n const localBundle = await createBundle();\n const remote = readRemoteGist(state.config.gist_id);\n const remoteHash = remote.parsed?.hash || \"\";\n const lastHash = state.config.last_synced_hash;\n let status = \"The Gist does not contain a Zed settings file.\";\n if (remoteHash === localBundle.hash) status = \"Synchronized\";\n else if (!lastHash) status = \"Not synchronized on this machine\";\n else if (localBundle.hash === lastHash) status = \"Remote changes available\";\n else if (remoteHash === lastHash) status = \"Local changes pending upload\";\n else status = \"Conflict: local and remote settings both changed\";\n nativeMessage(\n `Gist: ${remote.gist.html_url || state.config.gist_id}\\nStatus: ${status}\\n\\nLocal: ${shortHash(localBundle.hash)}\\nRemote: ${shortHash(remoteHash)}\\nLast sync: ${shortHash(lastHash)}\\nRemote updated: ${remote.gist.updated_at || \"unknown\"}`,\n \"Zed Settings Gist Status\",\n );\n}\n\nasync function main() {\n const [mode, explicitPath] = process.argv.slice(2);\n if (mode === \"export\") return exportBundle(resolve(explicitPath));\n if (mode === \"import\") return importBundle(resolve(explicitPath));\n if (mode === \"export-dialog\") {\n const path = nativeFileDialog(\"save\");\n if (path) await exportBundle(path);\n return;\n }\n if (mode === \"import-dialog\") {\n const path = nativeFileDialog(\"open\");\n if (path) await importBundle(path);\n return;\n }\n if (mode === \"gist-configure\") return configureGist();\n if (mode === \"gist-status\") return showGistStatus();\n if (mode === \"gist-push\") return pushGist();\n if (mode === \"gist-pull\") return pullGist();\n throw new Error(`Unknown mode: ${mode || \"<missing>\"}`);\n}\n\ntry {\n await main();\n} catch (error) {\n console.error(error.stack || error.message);\n try {\n nativeMessage(error.message, \"Zed Settings Sync Error\", true);\n } catch {\n // The terminal output remains available when the native dialog backend is unavailable.\n }\n process.exitCode = 1;\n}\n",
|
|
"settings.json": "// Zed settings\n//\n// For information on how to configure Zed, see the Zed\n// documentation: https://zed.dev/docs/configuring-zed\n//\n// To see all of Zed's default settings without changing your\n// custom settings, run `zed: open default settings` from the\n// command palette (cmd-shift-p / ctrl-shift-p)\n{\n \"cli_default_open_behavior\": \"existing_window\",\n \"icon_theme\": \"Material Icon Theme\",\n \"auto_install_extensions\": {\n \"dockerfile\": true,\n \"env\": true,\n \"html\": true,\n \"material-icon-theme\": true,\n \"mcp-server-context7\": true,\n \"mcp-server-github\": true,\n \"one-dark-pro\": true,\n \"powershell\": true,\n \"toml\": true\n },\n \"disable_ai\": false,\n \"proxy\": \"\",\n \"agent\": {\n \"sidebar_side\": \"right\",\n \"favorite_models\": [],\n \"model_parameters\": []\n },\n \"project_panel\": {\n \"dock\": \"left\"\n },\n \"telemetry\": {\n \"diagnostics\": false,\n \"metrics\": false,\n \"anthropic_retention\": false\n },\n \"terminal\": {\n \"font_size\": 16.0,\n \"cursor_shape\": \"bar\"\n },\n \"base_keymap\": \"VSCode\",\n \"file_scan_exclusions\": [\n \"**/.git\",\n \"**/.svn\",\n \"**/.hg\",\n \"**/.jj\",\n \"**/CVS\",\n \"**/.DS_Store\",\n \"**/Thumbs.db\",\n \"**/.classpath\",\n \"**/.settings\",\n \"**/.playwright-cli\",\n \"**/output\",\n \"**/.cursor\",\n \"**/.vscode\",\n \"**/.idea\",\n \"**/.fleet\",\n \"**/node_modules\",\n \"**/bun.lockb\",\n \"**/pnpm-lock.yaml\",\n \"**/package-lock.json\",\n \"**/yarn.lock\",\n \"**/dist\",\n \"**/out\",\n \"**/build\",\n \"**/.next\",\n \"**/.nuxt\",\n \"**/.output\",\n \"**/.cache\",\n \"**/.turbo\",\n \"**/.vercel\",\n \"**/.netlify\",\n \"**/tsconfig.tsbuildinfo\",\n \"**/auto-imports.d.ts\",\n \"**/components.json\",\n \"**/unplugin-vue-components.d.ts\",\n \"src/*.d.ts\",\n \"**/__pycache__\",\n \"**/__init__.py\",\n \"**/.ipynb_checkpoints\",\n \"**/.pytest_cache\",\n \"**/.mypy_cache\",\n \"**/.venv\",\n \"**/env\",\n \"**/venv\",\n \"**/.github\",\n \"**/.husky\",\n \"**/.npmrc\",\n \"**/.eslintrc*\",\n \"**/.prettier*\",\n \"**/.editorconfig\",\n \"**/.dockerignore\",\n \"**/docker-compose.override.yml\",\n \"**/cypress*\",\n \"**/prisma/migrations\"\n ],\n \"preview_tabs\": {\n \"enabled\": false\n },\n \"git_panel\": {\n \"fallback_branch_name\": \"master\"\n },\n \"sticky_scroll\": {\n \"enabled\": true\n },\n \"minimap\": {\n \"show\": \"always\"\n },\n \"hover_popover_delay\": 250,\n \"cursor_blink\": true,\n \"autosave\": \"on_focus_change\",\n \"buffer_font_fallbacks\": [\n \"Consolas\",\n \"Sarasa Gothic SC\",\n \"Courier New\",\n \"monospace\"\n ],\n \"buffer_font_family\": \"Fira Code\",\n \"auto_indent_on_paste\": true,\n \"linked_edits\": true,\n \"show_edit_predictions\": true,\n \"line_ending\": \"prefer_lf\",\n \"hard_tabs\": false,\n \"tab_size\": 2,\n \"agent_servers\": {\n \"codex-acp\": {\n \"type\": \"registry\"\n }\n },\n \"ui_font_size\": 16,\n \"buffer_font_size\": 16.0,\n \"theme\": {\n \"mode\": \"system\",\n \"light\": \"One Light\",\n \"dark\": \"One Dark Pro\",\n },\n}\n",
|
|
"tasks.json": "// Project tasks configuration. See https://zed.dev/docs/tasks for documentation.\r\n//\n// Example:\n[\n /* The generated example task is intentionally disabled.\n {\n \"label\": \"Example task\",\r\n \"command\": \"for i in {1..5}; do echo \\\"Hello $i/5\\\"; sleep 1; done\",\r\n //\"args\": [],\r\n // Env overrides for the command, will be appended to the terminal's environment from the settings.\r\n \"env\": { \"foo\": \"bar\" },\r\n // Current working directory to spawn the command into, defaults to current project root.\r\n //\"cwd\": \"/path/to/working/directory\",\r\n // Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`.\r\n \"use_new_terminal\": false,\r\n // Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`.\r\n \"allow_concurrent_runs\": false,\r\n // What to do with the terminal pane and tab, after the command was started:\r\n // * `always` — always show the task's pane, and focus the corresponding tab in it (default)\r\n // * `no_focus` — always show the task's pane, add the task's tab in it, but don't focus it\r\n // * `never` — do not alter focus, but still add/reuse the task's tab in its pane\r\n \"reveal\": \"always\",\r\n // Where to place the task's terminal item after starting the task:\r\n // * `dock` — in the terminal dock, \"regular\" terminal items' place (default)\r\n // * `center` — in the central pane group, \"main\" editor area\r\n \"reveal_target\": \"dock\",\r\n // What to do with the terminal pane and tab, after the command had finished:\r\n // * `never` — Do nothing when the command finishes (default)\r\n // * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it\r\n // * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`\r\n \"hide\": \"never\",\r\n // Which shell to use when running a task inside the terminal.\r\n // May take 3 values:\r\n // 1. (default) Use the system's default terminal configuration in /etc/passwd\r\n // \"shell\": \"system\"\r\n // 2. A program:\r\n // \"shell\": {\r\n // \"program\": \"sh\"\r\n // }\r\n // 3. A program with arguments:\r\n // \"shell\": {\r\n // \"with_arguments\": {\r\n // \"program\": \"/bin/bash\",\r\n // \"args\": [\"--login\"]\r\n // }\r\n // }\r\n \"shell\": \"system\",\r\n // Whether to show the task line in the output of the spawned task, defaults to `true`.\r\n \"show_summary\": true,\r\n // Whether to show the command line in the output of the spawned task, defaults to `true`.\r\n \"show_command\": true,\r\n // Which edited buffers to save before running the task:\r\n // * `all` — save all edited buffers\r\n // * `current` — save currently active buffer only\r\n // * `none` — don't save any buffers\r\n \"save\": \"none\",\r\n // Represents the tags for inline runnable indicators, or spawning multiple tasks at once.\r\n // \"tags\": []\n }, */\n {\n \"label\": \"Toggle excluded files\",\n \"command\": \"node\",\n \"args\": [\n \"__TOGGLE_SCRIPT__\"\n ],\n \"use_new_terminal\": false,\n \"allow_concurrent_runs\": false,\n \"reveal\": \"never\",\n \"hide\": \"always\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"none\"\n },\n {\n \"label\": \"Zed Settings: Export JSON...\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"export-dialog\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"never\",\n \"hide\": \"always\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Import JSON...\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"import-dialog\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"never\",\n \"hide\": \"always\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Configure GitHub Gist...\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"gist-configure\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"no_focus\",\n \"hide\": \"never\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Show GitHub Gist Status\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"gist-status\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"no_focus\",\n \"hide\": \"never\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Push to GitHub Gist\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"gist-push\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"no_focus\",\n \"hide\": \"never\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Pull from GitHub Gist\",\n \"command\": \"node\",\n \"args\": [\n \"__SYNC_SCRIPT__\",\n \"gist-pull\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"no_focus\",\n \"hide\": \"never\",\n \"show_summary\": false,\n \"show_command\": false,\n \"save\": \"all\"\n },\n {\n \"label\": \"Zed Settings: Authenticate GitHub...\",\n \"command\": \"powershell\",\n \"args\": [\n \"-NoExit\",\n \"-Command\",\n \"Remove-Item Env:GH_TOKEN,Env:GITHUB_TOKEN -ErrorAction SilentlyContinue; gh auth login\"\n ],\n \"allow_concurrent_runs\": false,\n \"reveal\": \"always\",\n \"hide\": \"never\",\n \"show_summary\": false,\n \"show_command\": true,\n \"save\": \"none\"\n },\n]\n"
|
|
}
|