installBindingsForVSCodeTerminal function

Future<String> installBindingsForVSCodeTerminal({
  1. String editor = 'VSCode',
})

Install Shift+Enter keybinding for VSCode, Cursor, or Windsurf.

Creates/modifies the keybindings.json file in the editor's user directory. Backs up existing file before modification.

Implementation

Future<String> installBindingsForVSCodeTerminal({
  String editor = 'VSCode',
}) async {
  // Check if we're running in a VSCode Remote SSH session.
  if (isVSCodeRemoteSSH()) {
    return 'Cannot install keybindings from a remote $editor session.\n\n'
        '$editor keybindings must be installed on your local machine, '
        'not the remote server.\n\n'
        'To install the Shift+Enter keybinding:\n'
        '1. Open $editor on your local machine (not connected to remote)\n'
        '2. Open the Command Palette (Cmd/Ctrl+Shift+P) -> '
        '"Preferences: Open Keyboard Shortcuts (JSON)"\n'
        '3. Add this keybinding (the file must be a JSON array):\n\n'
        '[\n'
        '  {\n'
        '    "key": "shift+enter",\n'
        '    "command": "workbench.action.terminal.sendSequence",\n'
        '    "args": { "text": "\\u001b\\r" },\n'
        '    "when": "terminalFocus"\n'
        '  }\n'
        ']\n';
  }

  final editorDir = editor == 'VSCode' ? 'Code' : editor;
  final home = Platform.environment['HOME'] ?? '';

  String userDirPath;
  if (Platform.isWindows) {
    final appData = Platform.environment['APPDATA'] ?? '';
    userDirPath = p.join(appData, editorDir, 'User');
  } else if (Platform.isMacOS) {
    userDirPath = p.join(
      home,
      'Library',
      'Application Support',
      editorDir,
      'User',
    );
  } else {
    userDirPath = p.join(home, '.config', editorDir, 'User');
  }

  final keybindingsPath = p.join(userDirPath, 'keybindings.json');

  try {
    // Ensure user directory exists.
    await Directory(userDirPath).create(recursive: true);

    // Read existing keybindings file.
    String content = '[]';
    List<dynamic> keybindings = [];
    bool fileExists = false;

    try {
      content = await File(keybindingsPath).readAsString();
      fileExists = true;
      // Strip comments for parsing (JSONC -> JSON).
      final stripped = _stripJsonComments(content);
      keybindings = jsonDecode(stripped) as List<dynamic>? ?? [];
    } catch (e) {
      if (e is! FileSystemException) rethrow;
    }

    // Backup the existing file before modifying.
    if (fileExists) {
      final randomSha = _randomHex(4);
      final backupPath = '$keybindingsPath.$randomSha.bak';
      try {
        await File(keybindingsPath).copy(backupPath);
      } catch (_) {
        return 'Error backing up existing $editor terminal keybindings. '
            'Bailing out.\n'
            'See $keybindingsPath\n'
            'Backup path: $backupPath\n';
      }
    }

    // Check if keybinding already exists.
    final existingBinding = keybindings.any((binding) {
      if (binding is! Map<String, dynamic>) return false;
      return binding['key'] == 'shift+enter' &&
          binding['command'] == 'workbench.action.terminal.sendSequence' &&
          binding['when'] == 'terminalFocus';
    });

    if (existingBinding) {
      return 'Found existing $editor terminal Shift+Enter key binding. '
          'Remove it to continue.\n'
          'See $keybindingsPath\n';
    }

    // Create the new keybinding.
    const newKeybinding = _VSCodeKeybinding(
      key: 'shift+enter',
      command: 'workbench.action.terminal.sendSequence',
      args: {'text': '\x1b\r'},
      when: 'terminalFocus',
    );

    // Add to the array and write back.
    keybindings.add(newKeybinding.toJson());
    final updatedContent = const JsonEncoder.withIndent(
      '  ',
    ).convert(keybindings);
    await File(keybindingsPath).writeAsString(updatedContent);

    return 'Installed $editor terminal Shift+Enter key binding\n'
        'See $keybindingsPath\n';
  } catch (e) {
    throw Exception(
      'Failed to install $editor terminal Shift+Enter key binding: $e',
    );
  }
}