Skip to content

Commit f513ee9

Browse files
committed
Attempt to add vscode support for terminal setup
1 parent b5d8dc7 commit f513ee9

1 file changed

Lines changed: 161 additions & 1 deletion

File tree

cecli/commands/terminal_setup.py

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ class TerminalSetupCommand(BaseCommand):
2323
}
2424

2525
WT_KEYBINDING = {"id": "User.sendInput.shift_enter", "keys": "shift+enter"}
26+
# VS Code configuration constants
27+
VSCODE_SHIFT_ENTER_SEQUENCE = "\n"
28+
29+
VSCODE_SHIFT_ENTER_BINDING = {
30+
"key": "shift+enter",
31+
"command": "workbench.action.terminal.sendSequence",
32+
"when": "terminalFocus",
33+
"args": {"text": "\n"},
34+
}
35+
36+
@staticmethod
37+
def _strip_json_comments(content: str) -> str:
38+
"""Remove single-line JSON comments (// ...) from a string to allow parsing
39+
VS Code style JSON files that may contain comments."""
40+
# Remove single-line comments (// ...)
41+
import re
42+
43+
return re.sub(r"^\s*//.*$", "", content, flags=re.MULTILINE)
2644

2745
@classmethod
2846
def _get_config_paths(cls):
@@ -38,6 +56,7 @@ def _get_config_paths(cls):
3856
# Standard Linux paths (applies to WSL instances of Kitty/Alacritty too)
3957
paths["alacritty"] = home / ".config" / "alacritty" / "alacritty.toml"
4058
paths["kitty"] = home / ".config" / "kitty" / "kitty.conf"
59+
paths["vscode"] = home / ".config" / "Code" / "User" / "keybindings.json"
4160

4261
if is_wsl_env:
4362
# Try to find Windows Terminal settings from inside WSL
@@ -62,11 +81,17 @@ def _get_config_paths(cls):
6281
elif system == "Darwin": # macOS
6382
paths["alacritty"] = home / ".config" / "alacritty" / "alacritty.toml"
6483
paths["kitty"] = home / ".config" / "kitty" / "kitty.conf"
84+
# VS Code on macOS
85+
paths["vscode"] = (
86+
home / "Library" / "Application Support" / "Code" / "User" / "keybindings.json"
87+
)
6588

6689
elif system == "Windows":
6790
appdata = Path(os.getenv("APPDATA"))
6891
paths["alacritty"] = appdata / "alacritty" / "alacritty.toml"
6992
paths["kitty"] = appdata / "kitty" / "kitty.conf"
93+
# VS Code on Windows
94+
paths["vscode"] = appdata / "Code" / "User" / "keybindings.json"
7095

7196
# Windows Terminal path is tricky (has a unique hash in folder name)
7297
# We look for the folder starting with Microsoft.WindowsTerminal
@@ -77,6 +102,10 @@ def _get_config_paths(cls):
77102
if wt_glob:
78103
paths["windows_terminal"] = wt_glob[0]
79104

105+
else: # Linux
106+
# VS Code on Linux
107+
paths["vscode"] = home / ".config" / "Code" / "User" / "keybindings.json"
108+
80109
return paths
81110

82111
@classmethod
@@ -325,6 +354,133 @@ def _update_windows_terminal(cls, path, io, dry_run=False):
325354
)
326355
return False
327356

357+
@classmethod
358+
def _update_vscode(cls, path, io, dry_run=False):
359+
"""Updates VS Code keybindings.json with shift+enter binding."""
360+
# Create directory if it doesn't exist
361+
path.parent.mkdir(parents=True, exist_ok=True)
362+
363+
if not path.exists():
364+
if dry_run:
365+
io.tool_output(f"DRY-RUN: VS Code keybindings.json doesn't exist at {path}")
366+
io.tool_output(
367+
"DRY-RUN: Would create file with binding:"
368+
f" {json.dumps(cls.VSCODE_SHIFT_ENTER_BINDING, indent=2)}"
369+
)
370+
return True
371+
else:
372+
io.tool_output(f"Creating VS Code keybindings.json at {path}")
373+
# Create file with our binding
374+
data = [cls.VSCODE_SHIFT_ENTER_BINDING]
375+
with open(path, "w", encoding="utf-8") as f:
376+
json.dump(data, f, indent=4)
377+
io.tool_output("Created VS Code config with shift+enter binding.")
378+
return True
379+
380+
if dry_run:
381+
io.tool_output(f"DRY-RUN: Would check VS Code keybindings at {path}")
382+
io.tool_output(
383+
"DRY-RUN: Would add binding:"
384+
f" {json.dumps(cls.VSCODE_SHIFT_ENTER_BINDING, indent=2)}"
385+
)
386+
# Simulate checking for duplicates
387+
try:
388+
content = ""
389+
with open(path, "r", encoding="utf-8") as f:
390+
content = f.read()
391+
392+
# Strip comments before parsing
393+
content_no_comments = cls._strip_json_comments(content)
394+
if content_no_comments.strip():
395+
data = json.loads(content_no_comments)
396+
else:
397+
data = []
398+
399+
# Check if binding already exists
400+
already_exists = False
401+
if isinstance(data, list):
402+
for binding in data:
403+
if isinstance(binding, dict):
404+
# Check if this is our shift+enter binding
405+
if (
406+
binding.get("key") == "shift+enter"
407+
and binding.get("command")
408+
== "workbench.action.terminal.sendSequence"
409+
and binding.get("when") == "terminalFocus"
410+
):
411+
already_exists = True
412+
break
413+
414+
if already_exists:
415+
io.tool_output("DRY-RUN: VS Code already configured.")
416+
return False
417+
else:
418+
io.tool_output("DRY-RUN: Would update VS Code config.")
419+
return True
420+
except json.JSONDecodeError:
421+
io.tool_output(
422+
"DRY-RUN: Error: Could not parse VS Code keybindings.json. Is it valid JSON?"
423+
)
424+
return False
425+
except Exception as e:
426+
io.tool_output(f"DRY-RUN: Error reading file: {e}")
427+
return False
428+
429+
cls._backup_file(path, io)
430+
431+
try:
432+
content = ""
433+
with open(path, "r", encoding="utf-8") as f:
434+
content = f.read()
435+
436+
# Strip comments before parsing
437+
content_no_comments = cls._strip_json_comments(content)
438+
if content_no_comments.strip():
439+
data = json.loads(content_no_comments)
440+
else:
441+
data = []
442+
443+
# Ensure data is a list
444+
if not isinstance(data, list):
445+
io.tool_output(
446+
"Error: VS Code keybindings.json should contain an array of keybindings."
447+
)
448+
return False
449+
450+
# Check if binding already exists
451+
already_exists = False
452+
for binding in data:
453+
if isinstance(binding, dict):
454+
# Check if this is our shift+enter binding
455+
if (
456+
binding.get("key") == "shift+enter"
457+
and binding.get("command") == "workbench.action.terminal.sendSequence"
458+
and binding.get("when") == "terminalFocus"
459+
):
460+
already_exists = True
461+
break
462+
463+
if already_exists:
464+
io.tool_output("VS Code already configured.")
465+
return False
466+
467+
# Add our binding
468+
data.append(cls.VSCODE_SHIFT_ENTER_BINDING)
469+
470+
# Write back to file
471+
with open(path, "w", encoding="utf-8") as f:
472+
json.dump(data, f, indent=4)
473+
474+
io.tool_output("Updated VS Code config.")
475+
return True
476+
477+
except json.JSONDecodeError:
478+
io.tool_output("Error: Could not parse VS Code keybindings.json. Is it valid JSON?")
479+
return False
480+
except Exception as e:
481+
io.tool_output(f"Error updating VS Code config: {e}")
482+
return False
483+
328484
@classmethod
329485
async def execute(cls, io, coder, args, **kwargs):
330486
"""Configure terminal config files to support shift+enter for newline."""
@@ -350,6 +506,10 @@ async def execute(cls, io, coder, args, **kwargs):
350506
if cls._update_windows_terminal(paths["windows_terminal"], io, dry_run=dry_run):
351507
updated = True
352508

509+
if "vscode" in paths:
510+
if cls._update_vscode(paths["vscode"], io, dry_run=dry_run):
511+
updated = True
512+
353513
if dry_run:
354514
if updated:
355515
io.tool_output(
@@ -392,7 +552,7 @@ def get_help(cls) -> str:
392552
)
393553
help_text += (
394554
"\nNote: This command modifies terminal configuration files (Alacritty, Kitty, Windows"
395-
" Terminal)\n"
555+
" Terminal, VS Code)\n"
396556
)
397557
help_text += (
398558
"to add a key binding that sends a newline character when shift+enter is pressed.\n"

0 commit comments

Comments
 (0)