style: 完成所有文件的lint

This commit is contained in:
claude-code-best
2026-05-01 21:39:30 +08:00
parent d136872cc9
commit 6182015005
1333 changed files with 68255 additions and 77882 deletions

View File

@@ -1,35 +1,32 @@
import chalk from 'chalk'
import { randomBytes } from 'crypto'
import { copyFile, mkdir, readFile, writeFile } from 'fs/promises'
import { homedir, platform } from 'os'
import { dirname, join } from 'path'
import type { ThemeName } from 'src/utils/theme.js'
import { pathToFileURL } from 'url'
import { supportsHyperlinks } from '@anthropic/ink'
import { color } from '@anthropic/ink'
import { maybeMarkProjectOnboardingComplete } from '../../projectOnboardingState.js'
import type { ToolUseContext } from '../../Tool.js'
import type {
LocalJSXCommandContext,
LocalJSXCommandOnDone,
} from '../../types/command.js'
import chalk from 'chalk';
import { randomBytes } from 'crypto';
import { copyFile, mkdir, readFile, writeFile } from 'fs/promises';
import { homedir, platform } from 'os';
import { dirname, join } from 'path';
import type { ThemeName } from 'src/utils/theme.js';
import { pathToFileURL } from 'url';
import { supportsHyperlinks } from '@anthropic/ink';
import { color } from '@anthropic/ink';
import { maybeMarkProjectOnboardingComplete } from '../../projectOnboardingState.js';
import type { ToolUseContext } from '../../Tool.js';
import type { LocalJSXCommandContext, LocalJSXCommandOnDone } from '../../types/command.js';
import {
backupTerminalPreferences,
checkAndRestoreTerminalBackup,
getTerminalPlistPath,
markTerminalSetupComplete,
} from '../../utils/appleTerminalBackup.js'
import { setupShellCompletion } from '../../utils/completionCache.js'
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
import { env } from '../../utils/env.js'
import { isFsInaccessible } from '../../utils/errors.js'
import { execFileNoThrow } from '../../utils/execFileNoThrow.js'
import { addItemToJSONCArray, safeParseJSONC } from '../../utils/json.js'
import { logError } from '../../utils/log.js'
import { getPlatform } from '../../utils/platform.js'
import { jsonParse, jsonStringify } from '../../utils/slowOperations.js'
} from '../../utils/appleTerminalBackup.js';
import { setupShellCompletion } from '../../utils/completionCache.js';
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
import { env } from '../../utils/env.js';
import { isFsInaccessible } from '../../utils/errors.js';
import { execFileNoThrow } from '../../utils/execFileNoThrow.js';
import { addItemToJSONCArray, safeParseJSONC } from '../../utils/json.js';
import { logError } from '../../utils/log.js';
import { getPlatform } from '../../utils/platform.js';
import { jsonParse, jsonStringify } from '../../utils/slowOperations.js';
const EOL = '\n'
const EOL = '\n';
// Terminals that natively support CSI u / Kitty keyboard protocol
const NATIVE_CSIU_TERMINALS: Record<string, string> = {
@@ -38,7 +35,7 @@ const NATIVE_CSIU_TERMINALS: Record<string, string> = {
'iTerm.app': 'iTerm2',
WezTerm: 'WezTerm',
WarpTerminal: 'Warp',
}
};
/**
* Detect if we're running in a VSCode Remote SSH session.
@@ -46,8 +43,8 @@ const NATIVE_CSIU_TERMINALS: Record<string, string> = {
* not the remote server where Claude is running.
*/
function isVSCodeRemoteSSH(): boolean {
const askpassMain = process.env.VSCODE_GIT_ASKPASS_MAIN ?? ''
const path = process.env.PATH ?? ''
const askpassMain = process.env.VSCODE_GIT_ASKPASS_MAIN ?? '';
const path = process.env.PATH ?? '';
// Check both env vars - VSCODE_GIT_ASKPASS_MAIN is more reliable when git extension
// is active, and PATH is a fallback. Omit path separator for Windows compatibility.
@@ -58,14 +55,14 @@ function isVSCodeRemoteSSH(): boolean {
path.includes('.vscode-server') ||
path.includes('.cursor-server') ||
path.includes('.windsurf-server')
)
);
}
export function getNativeCSIuTerminalDisplayName(): string | null {
if (!env.terminal || !(env.terminal in NATIVE_CSIU_TERMINALS)) {
return null
return null;
}
return NATIVE_CSIU_TERMINALS[env.terminal] ?? null
return NATIVE_CSIU_TERMINALS[env.terminal] ?? null;
}
/**
@@ -81,11 +78,11 @@ export function getNativeCSIuTerminalDisplayName(): string | null {
*/
function formatPathLink(filePath: string): string {
if (!supportsHyperlinks()) {
return filePath
return filePath;
}
const fileUrl = pathToFileURL(filePath).href
const fileUrl = pathToFileURL(filePath).href;
// OSC 8 hyperlink: \e]8;;URL\a TEXT \e]8;;\a
return `\x1b]8;;${fileUrl}\x07${filePath}\x1b]8;;\x07`
return `\x1b]8;;${fileUrl}\x07${filePath}\x1b]8;;\x07`;
}
export function shouldOfferTerminalSetup(): boolean {
@@ -99,75 +96,71 @@ export function shouldOfferTerminalSetup(): boolean {
env.terminal === 'windsurf' ||
env.terminal === 'alacritty' ||
env.terminal === 'zed'
)
);
}
export async function setupTerminal(theme: ThemeName): Promise<string> {
let result = ''
let result = '';
switch (env.terminal) {
case 'Apple_Terminal':
result = await enableOptionAsMetaForTerminal(theme)
break
result = await enableOptionAsMetaForTerminal(theme);
break;
case 'vscode':
result = await installBindingsForVSCodeTerminal('VSCode', theme)
break
result = await installBindingsForVSCodeTerminal('VSCode', theme);
break;
case 'cursor':
result = await installBindingsForVSCodeTerminal('Cursor', theme)
break
result = await installBindingsForVSCodeTerminal('Cursor', theme);
break;
case 'windsurf':
result = await installBindingsForVSCodeTerminal('Windsurf', theme)
break
result = await installBindingsForVSCodeTerminal('Windsurf', theme);
break;
case 'alacritty':
result = await installBindingsForAlacritty(theme)
break
result = await installBindingsForAlacritty(theme);
break;
case 'zed':
result = await installBindingsForZed(theme)
break
result = await installBindingsForZed(theme);
break;
case null:
break
break;
}
saveGlobalConfig(current => {
if (
['vscode', 'cursor', 'windsurf', 'alacritty', 'zed'].includes(
env.terminal ?? '',
)
) {
if (current.shiftEnterKeyBindingInstalled === true) return current
return { ...current, shiftEnterKeyBindingInstalled: true }
if (['vscode', 'cursor', 'windsurf', 'alacritty', 'zed'].includes(env.terminal ?? '')) {
if (current.shiftEnterKeyBindingInstalled === true) return current;
return { ...current, shiftEnterKeyBindingInstalled: true };
} else if (env.terminal === 'Apple_Terminal') {
if (current.optionAsMetaKeyInstalled === true) return current
return { ...current, optionAsMetaKeyInstalled: true }
if (current.optionAsMetaKeyInstalled === true) return current;
return { ...current, optionAsMetaKeyInstalled: true };
}
return current
})
return current;
});
maybeMarkProjectOnboardingComplete()
maybeMarkProjectOnboardingComplete();
// Install shell completions (ant-only, since the completion command is ant-only)
if (process.env.USER_TYPE === 'ant') {
result += await setupShellCompletion(theme)
result += await setupShellCompletion(theme);
}
return result
return result;
}
export function isShiftEnterKeyBindingInstalled(): boolean {
return getGlobalConfig().shiftEnterKeyBindingInstalled === true
return getGlobalConfig().shiftEnterKeyBindingInstalled === true;
}
export function hasUsedBackslashReturn(): boolean {
return getGlobalConfig().hasUsedBackslashReturn === true
return getGlobalConfig().hasUsedBackslashReturn === true;
}
export function markBackslashReturnUsed(): void {
const config = getGlobalConfig()
const config = getGlobalConfig();
if (!config.hasUsedBackslashReturn) {
saveGlobalConfig(current => ({
...current,
hasUsedBackslashReturn: true,
}))
}));
}
}
@@ -179,22 +172,22 @@ export async function call(
if (env.terminal && env.terminal in NATIVE_CSIU_TERMINALS) {
const message = `Shift+Enter is natively supported in ${NATIVE_CSIU_TERMINALS[env.terminal]}.
No configuration needed. Just use Shift+Enter to add newlines.`
onDone(message)
return null
No configuration needed. Just use Shift+Enter to add newlines.`;
onDone(message);
return null;
}
// Check if terminal is supported
if (!shouldOfferTerminalSetup()) {
const terminalName = env.terminal || 'your current terminal'
const currentPlatform = getPlatform()
const terminalName = env.terminal || 'your current terminal';
const currentPlatform = getPlatform();
// Build platform-specific terminal suggestions
let platformTerminals = ''
let platformTerminals = '';
if (currentPlatform === 'macos') {
platformTerminals = ' • macOS: Apple Terminal\n'
platformTerminals = ' • macOS: Apple Terminal\n';
} else if (currentPlatform === 'windows') {
platformTerminals = ' • Windows: Windows Terminal\n'
platformTerminals = ' • Windows: Windows Terminal\n';
}
// For Linux and other platforms, we don't show native terminal options
// since they're not currently supported
@@ -211,22 +204,22 @@ ${platformTerminals} • IDE: VSCode, Cursor, Windsurf, Zed
• Other: Alacritty
3. Return to tmux/screen - settings will persist
${chalk.dim('Note: iTerm2, WezTerm, Ghostty, Kitty, and Warp support Shift+Enter natively.')}`
onDone(message)
return null
${chalk.dim('Note: iTerm2, WezTerm, Ghostty, Kitty, and Warp support Shift+Enter natively.')}`;
onDone(message);
return null;
}
const result = await setupTerminal(context.options.theme)
onDone(result)
return null
const result = await setupTerminal(context.options.theme);
onDone(result);
return null;
}
type VSCodeKeybinding = {
key: string
command: string
args: { text: string }
when: string
}
key: string;
command: string;
args: { text: string };
when: string;
};
async function installBindingsForVSCodeTerminal(
editor: 'VSCode' | 'Cursor' | 'Windsurf' = 'VSCode',
@@ -247,10 +240,10 @@ async function installBindingsForVSCodeTerminal(
"args": { "text": "\\u001b\\r" },
"when": "terminalFocus"
}
]`)}${EOL}`
]`)}${EOL}`;
}
const editorDir = editor === 'VSCode' ? 'Code' : editor
const editorDir = editor === 'VSCode' ? 'Code' : editor;
const userDirPath = join(
homedir(),
platform() === 'win32'
@@ -258,38 +251,38 @@ async function installBindingsForVSCodeTerminal(
: platform() === 'darwin'
? join('Library', 'Application Support', editorDir, 'User')
: join('.config', editorDir, 'User'),
)
const keybindingsPath = join(userDirPath, 'keybindings.json')
);
const keybindingsPath = join(userDirPath, 'keybindings.json');
try {
// Ensure user directory exists (idempotent with recursive)
await mkdir(userDirPath, { recursive: true })
await mkdir(userDirPath, { recursive: true });
// Read existing keybindings file, or default to empty array if it doesn't exist
let content = '[]'
let keybindings: VSCodeKeybinding[] = []
let fileExists = false
let content = '[]';
let keybindings: VSCodeKeybinding[] = [];
let fileExists = false;
try {
content = await readFile(keybindingsPath, { encoding: 'utf-8' })
fileExists = true
keybindings = (safeParseJSONC(content) as VSCodeKeybinding[]) ?? []
content = await readFile(keybindingsPath, { encoding: 'utf-8' });
fileExists = true;
keybindings = (safeParseJSONC(content) as VSCodeKeybinding[]) ?? [];
} catch (e: unknown) {
if (!isFsInaccessible(e)) throw e
if (!isFsInaccessible(e)) throw e;
}
// Backup the existing file before modifying it
if (fileExists) {
const randomSha = randomBytes(4).toString('hex')
const backupPath = `${keybindingsPath}.${randomSha}.bak`
const randomSha = randomBytes(4).toString('hex');
const backupPath = `${keybindingsPath}.${randomSha}.bak`;
try {
await copyFile(keybindingsPath, backupPath)
await copyFile(keybindingsPath, backupPath);
} catch {
return `${color(
'warning',
theme,
)(
`Error backing up existing ${editor} terminal keybindings. Bailing out.`,
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`;
}
}
@@ -299,14 +292,14 @@ async function installBindingsForVSCodeTerminal(
binding.key === 'shift+enter' &&
binding.command === 'workbench.action.terminal.sendSequence' &&
binding.when === 'terminalFocus',
)
);
if (existingBinding) {
return `${color(
'warning',
theme,
)(
`Found existing ${editor} terminal Shift+Enter key binding. Remove it to continue.`,
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}`;
}
// Create the new keybinding
@@ -315,38 +308,34 @@ async function installBindingsForVSCodeTerminal(
command: 'workbench.action.terminal.sendSequence',
args: { text: '\u001b\r' },
when: 'terminalFocus',
}
};
// Modify the content by adding the new keybinding while preserving comments and formatting
const updatedContent = addItemToJSONCArray(content, newKeybinding)
const updatedContent = addItemToJSONCArray(content, newKeybinding);
// Write the updated content back to the file
await writeFile(keybindingsPath, updatedContent, { encoding: 'utf-8' })
await writeFile(keybindingsPath, updatedContent, { encoding: 'utf-8' });
return `${color(
'success',
theme,
)(
`Installed ${editor} terminal Shift+Enter key binding`,
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(keybindingsPath)}`)}${EOL}`;
} catch (error) {
logError(error)
throw new Error(
`Failed to install ${editor} terminal Shift+Enter key binding`,
)
logError(error);
throw new Error(`Failed to install ${editor} terminal Shift+Enter key binding`);
}
}
async function enableOptionAsMetaForProfile(
profileName: string,
): Promise<boolean> {
async function enableOptionAsMetaForProfile(profileName: string): Promise<boolean> {
// First try to add the property (in case it doesn't exist)
// Quote the profile name to handle names with spaces (e.g., "Man Page", "Red Sands")
const { code: addCode } = await execFileNoThrow('/usr/libexec/PlistBuddy', [
'-c',
`Add :'Window Settings':'${profileName}':useOptionAsMetaKey bool true`,
getTerminalPlistPath(),
])
]);
// If adding fails (likely because it already exists), try setting it instead
if (addCode !== 0) {
@@ -354,31 +343,25 @@ async function enableOptionAsMetaForProfile(
'-c',
`Set :'Window Settings':'${profileName}':useOptionAsMetaKey true`,
getTerminalPlistPath(),
])
]);
if (setCode !== 0) {
logError(
new Error(
`Failed to enable Option as Meta key for Terminal.app profile: ${profileName}`,
),
)
return false
logError(new Error(`Failed to enable Option as Meta key for Terminal.app profile: ${profileName}`));
return false;
}
}
return true
return true;
}
async function disableAudioBellForProfile(
profileName: string,
): Promise<boolean> {
async function disableAudioBellForProfile(profileName: string): Promise<boolean> {
// First try to add the property (in case it doesn't exist)
// Quote the profile name to handle names with spaces (e.g., "Man Page", "Red Sands")
const { code: addCode } = await execFileNoThrow('/usr/libexec/PlistBuddy', [
'-c',
`Add :'Window Settings':'${profileName}':Bell bool false`,
getTerminalPlistPath(),
])
]);
// If adding fails (likely because it already exists), try setting it instead
if (addCode !== 0) {
@@ -386,114 +369,98 @@ async function disableAudioBellForProfile(
'-c',
`Set :'Window Settings':'${profileName}':Bell false`,
getTerminalPlistPath(),
])
]);
if (setCode !== 0) {
logError(
new Error(
`Failed to disable audio bell for Terminal.app profile: ${profileName}`,
),
)
return false
logError(new Error(`Failed to disable audio bell for Terminal.app profile: ${profileName}`));
return false;
}
}
return true
return true;
}
// Enable Option as Meta key for Terminal.app
async function enableOptionAsMetaForTerminal(
theme: ThemeName,
): Promise<string> {
async function enableOptionAsMetaForTerminal(theme: ThemeName): Promise<string> {
try {
// Create a backup of the current plist file
const backupPath = await backupTerminalPreferences()
const backupPath = await backupTerminalPreferences();
if (!backupPath) {
throw new Error(
'Failed to create backup of Terminal.app preferences, bailing out',
)
throw new Error('Failed to create backup of Terminal.app preferences, bailing out');
}
// Read the current default profile from the plist
const { stdout: defaultProfile, code: readCode } = await execFileNoThrow(
'defaults',
['read', 'com.apple.Terminal', 'Default Window Settings'],
)
const { stdout: defaultProfile, code: readCode } = await execFileNoThrow('defaults', [
'read',
'com.apple.Terminal',
'Default Window Settings',
]);
if (readCode !== 0 || !defaultProfile.trim()) {
throw new Error('Failed to read default Terminal.app profile')
throw new Error('Failed to read default Terminal.app profile');
}
const { stdout: startupProfile, code: startupCode } = await execFileNoThrow(
'defaults',
['read', 'com.apple.Terminal', 'Startup Window Settings'],
)
const { stdout: startupProfile, code: startupCode } = await execFileNoThrow('defaults', [
'read',
'com.apple.Terminal',
'Startup Window Settings',
]);
if (startupCode !== 0 || !startupProfile.trim()) {
throw new Error('Failed to read startup Terminal.app profile')
throw new Error('Failed to read startup Terminal.app profile');
}
let wasAnyProfileUpdated = false
let wasAnyProfileUpdated = false;
const defaultProfileName = defaultProfile.trim()
const optionAsMetaEnabled =
await enableOptionAsMetaForProfile(defaultProfileName)
const audioBellDisabled =
await disableAudioBellForProfile(defaultProfileName)
const defaultProfileName = defaultProfile.trim();
const optionAsMetaEnabled = await enableOptionAsMetaForProfile(defaultProfileName);
const audioBellDisabled = await disableAudioBellForProfile(defaultProfileName);
if (optionAsMetaEnabled || audioBellDisabled) {
wasAnyProfileUpdated = true
wasAnyProfileUpdated = true;
}
const startupProfileName = startupProfile.trim()
const startupProfileName = startupProfile.trim();
// Only proceed if the startup profile is different from the default profile
if (startupProfileName !== defaultProfileName) {
const startupOptionAsMetaEnabled =
await enableOptionAsMetaForProfile(startupProfileName)
const startupAudioBellDisabled =
await disableAudioBellForProfile(startupProfileName)
const startupOptionAsMetaEnabled = await enableOptionAsMetaForProfile(startupProfileName);
const startupAudioBellDisabled = await disableAudioBellForProfile(startupProfileName);
if (startupOptionAsMetaEnabled || startupAudioBellDisabled) {
wasAnyProfileUpdated = true
wasAnyProfileUpdated = true;
}
}
if (!wasAnyProfileUpdated) {
throw new Error(
'Failed to enable Option as Meta key or disable audio bell for any Terminal.app profile',
)
throw new Error('Failed to enable Option as Meta key or disable audio bell for any Terminal.app profile');
}
// Flush the preferences cache
await execFileNoThrow('killall', ['cfprefsd'])
await execFileNoThrow('killall', ['cfprefsd']);
markTerminalSetupComplete()
markTerminalSetupComplete();
return `${color(
'success',
theme,
)(
`Configured Terminal.app settings:`,
)}${EOL}${color('success', theme)('- Enabled "Use Option as Meta key"')}${EOL}${color('success', theme)('- Switched to visual bell')}${EOL}${chalk.dim('Option+Enter will now enter a newline.')}${EOL}${chalk.dim('You must restart Terminal.app for changes to take effect.', theme)}${EOL}`
)}${EOL}${color('success', theme)('- Enabled "Use Option as Meta key"')}${EOL}${color('success', theme)('- Switched to visual bell')}${EOL}${chalk.dim('Option+Enter will now enter a newline.')}${EOL}${chalk.dim('You must restart Terminal.app for changes to take effect.', theme)}${EOL}`;
} catch (error) {
logError(error)
logError(error);
// Attempt to restore from backup
const restoreResult = await checkAndRestoreTerminalBackup()
const restoreResult = await checkAndRestoreTerminalBackup();
const errorMessage = 'Failed to enable Option as Meta key for Terminal.app.'
const errorMessage = 'Failed to enable Option as Meta key for Terminal.app.';
if (restoreResult.status === 'restored') {
throw new Error(
`${errorMessage} Your settings have been restored from backup.`,
)
throw new Error(`${errorMessage} Your settings have been restored from backup.`);
} else if (restoreResult.status === 'failed') {
throw new Error(
`${errorMessage} Restoring from backup failed, try manually with: defaults import com.apple.Terminal ${restoreResult.backupPath}`,
)
);
} else {
throw new Error(
`${errorMessage} No backup was available to restore from.`,
)
throw new Error(`${errorMessage} No backup was available to restore from.`);
}
}
}
@@ -502,128 +469,122 @@ async function installBindingsForAlacritty(theme: ThemeName): Promise<string> {
const ALACRITTY_KEYBINDING = `[[keyboard.bindings]]
key = "Return"
mods = "Shift"
chars = "\\u001B\\r"`
chars = "\\u001B\\r"`;
// Get Alacritty config file paths in order of preference
const configPaths: string[] = []
const configPaths: string[] = [];
// XDG config path (Linux and macOS)
const xdgConfigHome = process.env.XDG_CONFIG_HOME
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
if (xdgConfigHome) {
configPaths.push(join(xdgConfigHome, 'alacritty', 'alacritty.toml'))
configPaths.push(join(xdgConfigHome, 'alacritty', 'alacritty.toml'));
} else {
configPaths.push(join(homedir(), '.config', 'alacritty', 'alacritty.toml'))
configPaths.push(join(homedir(), '.config', 'alacritty', 'alacritty.toml'));
}
// Windows-specific path
if (platform() === 'win32') {
const appData = process.env.APPDATA
const appData = process.env.APPDATA;
if (appData) {
configPaths.push(join(appData, 'alacritty', 'alacritty.toml'))
configPaths.push(join(appData, 'alacritty', 'alacritty.toml'));
}
}
// Find existing config file by attempting to read it, or use first preferred path
let configPath: string | null = null
let configContent = ''
let configExists = false
let configPath: string | null = null;
let configContent = '';
let configExists = false;
for (const path of configPaths) {
try {
configContent = await readFile(path, { encoding: 'utf-8' })
configPath = path
configExists = true
break
configContent = await readFile(path, { encoding: 'utf-8' });
configPath = path;
configExists = true;
break;
} catch (e: unknown) {
if (!isFsInaccessible(e)) throw e
if (!isFsInaccessible(e)) throw e;
// File missing or inaccessible — try next config path
}
}
// If no config exists, use the first path (XDG/default location)
if (!configPath) {
configPath = configPaths[0] ?? null
configPath = configPaths[0] ?? null;
}
if (!configPath) {
throw new Error('No valid config path found for Alacritty')
throw new Error('No valid config path found for Alacritty');
}
try {
if (configExists) {
// Check if keybinding already exists (look for Shift+Return binding)
if (
configContent.includes('mods = "Shift"') &&
configContent.includes('key = "Return"')
) {
if (configContent.includes('mods = "Shift"') && configContent.includes('key = "Return"')) {
return `${color(
'warning',
theme,
)(
'Found existing Alacritty Shift+Enter key binding. Remove it to continue.',
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}`;
}
// Create backup
const randomSha = randomBytes(4).toString('hex')
const backupPath = `${configPath}.${randomSha}.bak`
const randomSha = randomBytes(4).toString('hex');
const backupPath = `${configPath}.${randomSha}.bak`;
try {
await copyFile(configPath, backupPath)
await copyFile(configPath, backupPath);
} catch {
return `${color(
'warning',
theme,
)(
'Error backing up existing Alacritty config. Bailing out.',
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`;
}
} else {
// Ensure config directory exists (idempotent with recursive)
await mkdir(dirname(configPath), { recursive: true })
await mkdir(dirname(configPath), { recursive: true });
}
// Add the keybinding to the config
let updatedContent = configContent
let updatedContent = configContent;
if (configContent && !configContent.endsWith('\n')) {
updatedContent += '\n'
updatedContent += '\n';
}
updatedContent += '\n' + ALACRITTY_KEYBINDING + '\n'
updatedContent += '\n' + ALACRITTY_KEYBINDING + '\n';
// Write the updated config
await writeFile(configPath, updatedContent, { encoding: 'utf-8' })
await writeFile(configPath, updatedContent, { encoding: 'utf-8' });
return `${color(
'success',
theme,
)('Installed Alacritty Shift+Enter key binding')}${EOL}${color(
return `${color('success', theme)('Installed Alacritty Shift+Enter key binding')}${EOL}${color(
'success',
theme,
)(
'You may need to restart Alacritty for changes to take effect',
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(configPath)}`)}${EOL}`;
} catch (error) {
logError(error)
throw new Error('Failed to install Alacritty Shift+Enter key binding')
logError(error);
throw new Error('Failed to install Alacritty Shift+Enter key binding');
}
}
async function installBindingsForZed(theme: ThemeName): Promise<string> {
// Zed uses JSON keybindings similar to VSCode
const zedDir = join(homedir(), '.config', 'zed')
const keymapPath = join(zedDir, 'keymap.json')
const zedDir = join(homedir(), '.config', 'zed');
const keymapPath = join(zedDir, 'keymap.json');
try {
// Ensure zed directory exists (idempotent with recursive)
await mkdir(zedDir, { recursive: true })
await mkdir(zedDir, { recursive: true });
// Read existing keymap file, or default to empty array if it doesn't exist
let keymapContent = '[]'
let fileExists = false
let keymapContent = '[]';
let fileExists = false;
try {
keymapContent = await readFile(keymapPath, { encoding: 'utf-8' })
fileExists = true
keymapContent = await readFile(keymapPath, { encoding: 'utf-8' });
fileExists = true;
} catch (e: unknown) {
if (!isFsInaccessible(e)) throw e
if (!isFsInaccessible(e)) throw e;
}
if (fileExists) {
@@ -634,36 +595,36 @@ async function installBindingsForZed(theme: ThemeName): Promise<string> {
theme,
)(
'Found existing Zed Shift+Enter key binding. Remove it to continue.',
)}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}`;
}
// Create backup
const randomSha = randomBytes(4).toString('hex')
const backupPath = `${keymapPath}.${randomSha}.bak`
const randomSha = randomBytes(4).toString('hex');
const backupPath = `${keymapPath}.${randomSha}.bak`;
try {
await copyFile(keymapPath, backupPath)
await copyFile(keymapPath, backupPath);
} catch {
return `${color(
'warning',
theme,
)(
'Error backing up existing Zed keymap. Bailing out.',
)}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`
)}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}${chalk.dim(`Backup path: ${formatPathLink(backupPath)}`)}${EOL}`;
}
}
// Parse and modify the keymap
let keymap: Array<{
context?: string
bindings: Record<string, string | string[]>
}>
context?: string;
bindings: Record<string, string | string[]>;
}>;
try {
keymap = jsonParse(keymapContent)
keymap = jsonParse(keymapContent);
if (!Array.isArray(keymap)) {
keymap = []
keymap = [];
}
} catch {
keymap = []
keymap = [];
}
// Add the new keybinding for terminal context
@@ -672,21 +633,19 @@ async function installBindingsForZed(theme: ThemeName): Promise<string> {
bindings: {
'shift-enter': ['terminal::SendText', '\u001b\r'],
},
})
});
// Write the updated keymap
await writeFile(keymapPath, jsonStringify(keymap, null, 2) + '\n', {
encoding: 'utf-8',
})
});
return `${color(
'success',
theme,
)(
'Installed Zed Shift+Enter key binding',
)}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}`
)('Installed Zed Shift+Enter key binding')}${EOL}${chalk.dim(`See ${formatPathLink(keymapPath)}`)}${EOL}`;
} catch (error) {
logError(error)
throw new Error('Failed to install Zed Shift+Enter key binding')
logError(error);
throw new Error('Failed to install Zed Shift+Enter key binding');
}
}