Skip to content

Instantly share code, notes, and snippets.

@trungnt13
Last active April 26, 2023 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trungnt13/997f6d1fe2023d4841b6f6e3d421f04d to your computer and use it in GitHub Desktop.
Save trungnt13/997f6d1fe2023d4841b6f6e3d421f04d to your computer and use it in GitHub Desktop.
[Code] Convert vscode MacOS keybindings to Linux/Windows keybindings
"""
19/04/2023: MacOS
- cmd+shift+y: select all selected occurence
- cmd+ctrl+o: outline
20/04/2023: Search Editor (MacOS)
- ctrl+f: open
- ctrl+0 or 9: focus Next or prev
- cmd+[ or ]: next or prev input box
- better split vertical and horizontal (> 2 editor tabs)
24/04/2023: diff editor (MacOS)
- ctrl+0 or 9: next or prev diff
26/04/2023: sync settings (MacOS)
"""
from collections import defaultdict
import os
import re
import shutil
import sys
import warnings
try:
import jstyleson
except ImportError:
raise RuntimeError("jstyleson is not installed, it is needed for JSON with comments.")
###################################################################
# sync all keybindings
###################################################################
enable_sync_profiles = True
if sys.platform == "darwin":
profile_dir = os.path.expanduser("~/Library/Application Support/Code/User/profiles")
snippets_dir = os.path.expanduser("~/Library/Application Support/Code/User/snippets")
default_keys = os.path.expanduser("~/Library/Application Support/Code/User/keybindings.json")
default_settings = os.path.expanduser("~/Library/Application Support/Code/User/settings.json")
elif sys.platform == "win32":
warnings.warn("Windows is not supported yet")
elif sys.platform == "linux":
profile_dir = os.path.expanduser("~/.config/Code/User/profiles")
snippets_dir = os.path.expanduser("~/.config/Code/User/snippets")
default_keys = os.path.expanduser("~/.config/Code/User/keybindings.json")
default_settings = os.path.expanduser("~/.config/Code/User/settings.json")
else:
enable_sync_profiles = False
print(f"Unknown platform: {sys.platform}")
# ======== For now only sync the keybindings
if os.path.exists(profile_dir):
assert os.path.isfile(default_keys), f"Keybindings file not found {default_keys}"
assert os.path.isfile(default_settings), f"Settings file not found {default_settings}"
assert os.path.isdir(snippets_dir), f"Snippets dir not found {snippets_dir}"
files = defaultdict(keys=[default_keys], settings=[default_settings])
snippets = [snippets_dir]
for p in os.listdir(profile_dir):
p = os.path.join(profile_dir, p)
if not os.path.isdir(p):
continue
p_keys = os.path.join(p, "keybindings.json")
assert os.path.isfile(p_keys), f"Keybindings file not found in {p_keys}"
files["keys"].append(p_keys)
p_settings = os.path.join(p, "settings.json")
assert os.path.isfile(p_settings), f"Settings file not found in {p}"
files["settings"].append(p_settings)
p_snippets = os.path.join(p, "snippets")
assert os.path.isdir(p_snippets), f"Snippets dir not found in {p}"
snippets.append(p_snippets)
# sort the files by newest modification
for key, values in files.items():
values.sort(key=os.path.getmtime, reverse=True)
print("*" * 8, key, "*" * 8)
print(values[0], "will be synced to all other profiles:")
text_newest = open(values[0], mode="r", encoding="utf-8").read()
for i in values[1:]:
text_old = open(i, mode="r", encoding="utf-8").read()
if text_newest != text_old:
# copy or overwrite
shutil.copy(values[0], i)
print(" - ", i)
# sync snippets
snippets.sort(key=os.path.getmtime, reverse=True)
print("Syncing snippets from", snippets[0], "to")
for p_old in snippets[1:]:
print("***", p_old, "***")
for f_new in os.listdir(snippets[0]):
f_new = os.path.join(snippets[0], f_new)
f_old = os.path.join(p_old, os.path.basename(f_new))
text_new = open(f_new, mode="r", encoding="utf-8").read()
text_old = "" if not os.path.exists(f_old) else open(f_old, mode="r", encoding="utf-8").read()
if text_old != text_new:
print(" - ", f_old)
shutil.copy(f_new, f_old)
# path = os.path.expanduser("~/Library/Application Support/Code/User/profiles/-7d9d45a1/settings.json")
###################################################################
# Now only support convert MacOS keybindings to Windows or Linux
###################################################################
# if not macOS then exit
if sys.platform != "darwin":
raise RuntimeError("Only support MacOS")
###################################################################
# Load keybindings from known path
###################################################################
user_keys = os.path.expanduser("~/Library/Application Support/Code/User/keybindings.json")
if os.path.exists(user_keys) and os.path.isfile(user_keys):
print(f"Found {user_keys}, copying to /tmp/keys.json")
if os.path.exists("/tmp/keys.json"):
os.remove("/tmp/keys.json")
shutil.copyfile(user_keys, "/tmp/keys.json")
else:
raise RuntimeError(f"{user_keys} not found")
###################################################################
# Input and output path
###################################################################
input_file = "/tmp/keys.json"
# check if the json is valid
try:
jstyleson.loads(open(input_file, mode="r", encoding="utf-8").read())
except jstyleson.decoder.JSONDecodeError as e:
raise RuntimeError(f"Invalid json file: {input_file}") from e
output_file = os.path.join(os.path.dirname(input_file), "keys_new.json")
###################################################################
# Processing
###################################################################
keybindings = open(input_file, mode="r", encoding="utf-8").read()
keybindings_new = str(keybindings)
alt = re.compile(r'"key": ".*(alt).*"')
cmd = re.compile(r'"key": ".*(cmd).*"')
ctrl = re.compile(r'"key": ".*(ctrl).*"')
# NOTE: every modification change the position of the string
changes = defaultdict(list)
# do nothing for alt
for match in alt.finditer(keybindings_new):
match: re.Match
s = match.start(1)
e = match.end(1)
# Do this first, convert all ctrl to alt
match: re.Match = ctrl.search(keybindings_new)
while match:
s = match.start(1)
e = match.end(1)
keybindings_new = keybindings_new[:s] + "alt" + keybindings_new[e:]
changes["ctrl->alt"].append(match.group())
match = ctrl.search(keybindings_new)
# otherwise, this will create "ctrl+ctrl" cases, convert all cmd to ctrl
match: re.Match = cmd.search(keybindings_new)
while match:
s = match.start(1)
e = match.end(1)
keybindings_new = keybindings_new[:s] + "ctrl" + keybindings_new[e:]
changes["cmd->ctrl"].append(match.group())
match = cmd.search(keybindings_new)
# change all alt+ctrl to ctrl+alt
alt_ctrl = re.compile(r'"key": ".*(alt\+ctrl).*"')
match: re.Match = alt_ctrl.search(keybindings_new)
while match:
s = match.start(1)
e = match.end(1)
keybindings_new = keybindings_new[:s] + "ctrl+alt" + keybindings_new[e:]
changes["alt+ctrl->ctrl+alt"].append(match.group())
match = alt_ctrl.search(keybindings_new)
# change all alt+alt (ctrl+alt) to empty
alt_alt = re.compile(r'"key": ".*(alt).*(alt).*"')
match: re.Match = alt_alt.search(keybindings_new)
while match:
s, e = match.span()
keybindings_new = keybindings_new[:s] + '"key": ""' + keybindings_new[e:]
changes["alt+alt->empty"].append(match.group())
match = alt_alt.search(keybindings_new)
###################################################################
# Show the changes
###################################################################
print("Changes:")
for k, v in changes.items():
print(f" - {k:20s}: {len(v)}")
for i in v:
print(f" - {i}")
###################################################################
# Save the new keybindings
###################################################################
# save the new keybindings
with open(output_file, mode="w", encoding="utf-8") as f:
f.write(keybindings_new)
try:
jstyleson.load(open(output_file, mode="r", encoding="utf-8"))
except jstyleson.decoder.JSONDecodeError as e:
raise RuntimeError(f"Invalid json file: {output_file}") from e
else:
print("Success saving new keybindings to", output_file)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment