Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save trungnt13/1a9a6f27dade759d3bc642cf00d52c4d to your computer and use it in GitHub Desktop.

Select an option

Save trungnt13/1a9a6f27dade759d3bc642cf00d52c4d to your computer and use it in GitHub Desktop.
convert_cursor_keybindings_windows.py
#!/usr/bin/env python3
import argparse
import json
from pathlib import Path
DEFAULT_SOURCE = Path("/Users/trungnt13/Library/Application Support/Cursor/User/keybindings.json")
DEFAULT_OUTPUT = Path("/Users/trungnt13/Downloads/keybindings.windows.json")
MODIFIER_MAP = {
"cmd": "ctrl",
"ctrl": "alt",
}
MODIFIER_ORDER = {
"ctrl": 0,
"alt": 1,
"shift": 2,
"win": 3,
"meta": 4,
"cmd": 5,
}
def normalize_chord(chord: str) -> tuple[str, bool]:
tokens = [token.strip().lower() for token in chord.split("+") if token.strip()]
remapped = [MODIFIER_MAP.get(token, token) for token in tokens]
modifiers = []
primary = []
duplicate_removed = False
seen_modifiers = set()
for token in remapped:
if token in MODIFIER_ORDER:
if token in seen_modifiers:
duplicate_removed = True
continue
seen_modifiers.add(token)
modifiers.append(token)
else:
primary.append(token)
modifiers.sort(key=lambda token: MODIFIER_ORDER[token])
normalized = "+".join(modifiers + primary)
return normalized, duplicate_removed
def convert_key(key: str) -> tuple[str, bool]:
chords = key.split(" ")
converted = []
duplicate_removed = False
for chord in chords:
normalized, chord_duplicate_removed = normalize_chord(chord)
converted.append(normalized)
duplicate_removed = duplicate_removed or chord_duplicate_removed
return " ".join(converted), duplicate_removed
def convert_bindings(bindings: list[dict]) -> tuple[list[dict], int, int]:
converted_bindings = []
changed_keys = 0
duplicate_removed_count = 0
for binding in bindings:
converted_binding = dict(binding)
key = binding.get("key")
if isinstance(key, str):
converted_key, duplicate_removed = convert_key(key)
converted_binding["key"] = converted_key
if converted_key != key:
changed_keys += 1
if duplicate_removed:
duplicate_removed_count += 1
converted_bindings.append(converted_binding)
return converted_bindings, changed_keys, duplicate_removed_count
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Convert Cursor macOS keybindings into a Windows-oriented variant."
)
parser.add_argument(
"--source",
type=Path,
default=DEFAULT_SOURCE,
help=f"Source keybindings JSON file. Default: {DEFAULT_SOURCE}",
)
parser.add_argument(
"--output",
type=Path,
default=DEFAULT_OUTPUT,
help=f"Output JSON file. Default: {DEFAULT_OUTPUT}",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
bindings = json.loads(args.source.read_text())
converted_bindings, changed_keys, duplicate_removed_count = convert_bindings(bindings)
args.output.write_text(json.dumps(converted_bindings, indent=2) + "\n")
print(f"source: {args.source}")
print(f"output: {args.output}")
print(f"entries: {len(converted_bindings)}")
print(f"changed keys: {changed_keys}")
print(f"duplicate modifiers removed: {duplicate_removed_count}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment