Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save aont/c13fd9ea9d6f396d4740feb1ad50ceca to your computer and use it in GitHub Desktop.

Select an option

Save aont/c13fd9ea9d6f396d4740feb1ad50ceca to your computer and use it in GitHub Desktop.

A Simple Tool to Tidy Up Your VS Code Windows on Windows

This Python script is a small utility that keeps your Visual Studio Code (VS Code) windows tidy on Windows. Its goal is simple: keep one “default” VS Code window open and close the rest. If there isn’t a good candidate to keep, it opens a fresh, empty VS Code window for you.

What the Tool Does

  1. Reads VS Code status: It calls code --status (via Code.exe + cli.js) to get a snapshot of your current VS Code environment—version, processes, and a section called Workspace Stats listing windows and folders.

  2. Finds VS Code windows on your desktop: Using the Windows API (ctypes), it enumerates top-level windows, filters for common VS Code/Electron classes, and double-checks window titles for phrases like “Visual Studio Code” or “Code - OSS”.

  3. Decides which window to keep (“default”): Any VS Code window not listed in Workspace Stats is treated as the “default” candidate. The script keeps the first such window it finds.

  4. Closes the others: It sends a standard WM_CLOSE message to all other VS Code windows so they shut down gracefully.

  5. No candidate? Open a new one: If every existing window appears in Workspace Stats (so there’s no default), the script starts VS Code with an empty window and then closes all existing ones.

  6. Prints a clear JSON result: It outputs what it did (kept one and closed the rest, or launched a new one) along with window handles and titles.

How It Works Under the Hood

  • Parsing code --status: The script includes a custom parser that extracts header info, a process tree, and detailed Workspace Stats (per-window titles, folder counts, file types, etc.). It normalizes fields like version, OS, CPU, and memory into structured JSON for reliable use.

  • Window enumeration (Windows API): With EnumWindows, GetWindowTextW, GetClassNameW, and SendMessageW, it detects VS Code windows and closes them cleanly via WM_CLOSE—no force-kill required.

  • Robust launch path: If the code CLI isn’t directly callable, it falls back to running Code.exe with cli.js using ELECTRON_RUN_AS_NODE, which is how VS Code’s CLI works internally.

Why the “Default” Rule?

The script defines “default” as a VS Code window whose title does not appear in Workspace Stats. In practice, Workspace Stats tends to list workspaces that are actively indexing or opened with folders. A plain, empty window—or certain unusual cases—often won’t show up there, making it a good default to keep around for quick edits.

When This Is Useful

  • You often end up with too many VS Code windows. One run of this script keeps your desk clean by closing the clutter.

  • You want a predictable “one window” setup. It guarantees either one kept window or a new empty one.

  • You like automations. You can bind this script to a shortcut or run it before launching your daily session.

Limitations and Notes

  • Windows only: It uses the Windows API (user32), so it won’t run on macOS or Linux.

  • Title/class heuristics: Detection depends on common VS Code window class names and title keywords; unusual builds or custom titles could slip through.

  • Graceful close, not force-kill: If a window hangs or blocks closing (e.g., unsaved changes), Windows may still prompt you. The script doesn’t forcefully terminate processes.

  • Assumes standard VS Code install paths: It searches typical Program Files locations. Portable/custom installs might require tweaks.

Running It

  • Use Python 3 on Windows.
  • Make sure VS Code is installed in its usual location (or adjust the paths in the script).
  • Run the script from a terminal. It will print a JSON summary of what it did.

In short, this tool gives you a quick, deterministic way to keep one clean VS Code window around and close the rest—perfect for people who want to tame window sprawl with a single command.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
VS Code window management tool (Windows only)
- Parse the output of `code --status` and collect the list of window titles that are listed in Workspace Stats.
- Default workspace = a window whose title is **not** listed in Workspace Stats.
- Keep one candidate for the default workspace and close all other VS Code windows via WM_CLOSE.
- If no candidate exists, launch VS Code with an empty window.
Dependencies: None (standard library only)
"""
import sys
import os
import json
import re
import subprocess
from typing import List, Tuple, Optional, Dict
# ---------- Windows API (ctypes) ----------
import ctypes
from ctypes import wintypes
user32 = ctypes.WinDLL('user32', use_last_error=True)
EnumWindows = user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
IsWindowVisible = user32.IsWindowVisible
GetWindowTextW = user32.GetWindowTextW
GetWindowTextLengthW = user32.GetWindowTextLengthW
GetClassNameW = user32.GetClassNameW
GetWindowThreadProcessId = user32.GetWindowThreadProcessId
SendMessageW = user32.SendMessageW
WM_CLOSE = 0x0010
# Class names / title characteristics commonly seen on VS Code top-level windows
VCODE_WINDOW_CLASS_CANDIDATES = {
"Chrome_WidgetWin_1", # Electron/Chromium family (VS Code main)
"Chrome_WidgetWin_0",
}
# Conservative list of keywords that tend to appear in titles
VCODE_TITLE_KEYWORDS = [
"Visual Studio Code", # official name
"VS Code", # occasional abbreviation
"Code - OSS", # OSS build
]
# ---------- `code --status` parsing (ported/included parser) ----------
def read_code_status_text() -> str:
"""If stdin is empty, execute `code --status` and capture it. Try encodings in order."""
# data = sys.stdin.read()
# if data.strip():
# return data
# # Try the 'code' command first
# try:
# out = subprocess.check_output(["code", "--status"])
# for enc in ("utf-8", "cp932", "mbcs"):
# try:
# return out.decode(enc)
# except UnicodeDecodeError:
# continue
# return out.decode(errors="replace")
# except Exception:
# pass
# Directly launch Code.exe + cli.js (replicating the provided function in Python)
candidates = []
pf = r"C:\Program Files"
pf86 = r"C:\Program Files (x86)"
candidates.append(os.path.join(pf, "Microsoft VS Code"))
candidates.append(os.path.join(pf86, "Microsoft VS Code"))
for base in candidates:
code_exe = os.path.join(base, "Code.exe")
cli_js = os.path.join(base, "resources", "app", "out", "cli.js")
if os.path.exists(code_exe) and os.path.exists(cli_js):
env = os.environ.copy()
env["VSCODE_DEV"] = ""
env["ELECTRON_RUN_AS_NODE"] = "1"
try:
out = subprocess.check_output([code_exe, cli_js, "--status"], env=env)
for enc in ("utf-8", "cp932", "mbcs"):
try:
return out.decode(enc)
except UnicodeDecodeError:
continue
return out.decode(errors="replace")
except Exception as e:
last_err = e
continue
print("Failed to run `code --status` via both `code` and Code.exe.", file=sys.stderr)
sys.exit(1)
def parse_headers(lines, i):
headers, gpu, in_gpu = {}, {}, False
while i < len(lines):
line = lines[i].rstrip("\n")
if not line.strip():
j = i + 1
while j < len(lines) and not lines[j].strip():
j += 1
if j < len(lines) and (lines[j].lstrip().startswith("CPU %") or lines[j].startswith("Workspace Stats:")):
i = j
break
i += 1
continue
if in_gpu:
if re.match(r"^\s{2,}\S", line):
k, v = [s.strip() for s in re.split(r":\s+", line.strip(), maxsplit=1)] if ":" in line else (line.strip(), "")
gpu[k] = v
i += 1
continue
else:
headers["GPU Status"] = gpu
in_gpu = False
m = re.match(r"^([A-Za-z ]+):\s+(.*)$", line)
if m:
key, val = m.group(1).strip(), m.group(2).strip()
if key == "GPU Status":
in_gpu, gpu = True, {}
else:
headers[key] = val
i += 1
continue
if headers:
break
else:
i += 1
if in_gpu:
headers["GPU Status"] = gpu
return headers, i
def parse_process_table(lines, i):
while i < len(lines) and not lines[i].lstrip().startswith("CPU %"):
if lines[i].startswith("Workspace Stats:"):
return [], i
i += 1
if i >= len(lines): return [], i
i += 1
row_re = re.compile(r"^(?P<indent>\s*)(?P<cpu>-?\d+)\s+(?P<mem>-?\d+)\s+(?P<pid>-?\d+)\s+(?P<proc>.+?)\s*$")
stack, root = [], []
def mk(cpu, mem, pid, proc, lvl):
return {"cpu_percent": int(cpu), "mem_mb": int(mem), "pid": int(pid), "process": proc, "_lvl": lvl, "children": []}
while i < len(lines):
line = lines[i].rstrip("\n")
if not line.strip():
i += 1; continue
if line.startswith("Workspace Stats:"):
break
m = row_re.match(line)
if not m: break
lvl = len(m.group("indent")) // 2
node = mk(m.group("cpu"), m.group("mem"), m.group("pid"), m.group("proc"), lvl)
if not stack:
root.append(node); stack.append(node)
else:
while stack and stack[-1]["_lvl"] >= lvl:
stack.pop()
(stack[-1]["children"] if stack else root).append(node)
stack.append(node)
i += 1
def strip(nodes):
for n in nodes:
n.pop("_lvl", None)
if n["children"]: strip(n["children"])
else: n.pop("children", None)
strip(root)
return root, i
def parse_workspace_stats(lines, i):
while i < len(lines) and not lines[i].startswith("Workspace Stats:"):
i += 1
if i >= len(lines): return None, i
i += 1 # skip header
windows = []
current_window = None
current_folder = None
win_re = re.compile(r'^\|\s+Window\s+\((?P<title>.+)\)\s*$')
fol_re = re.compile(r'^\|\s{2,}Folder\s+\((?P<name>.+)\):\s+(?P<count>\d+)\s+files\s*$')
types_re = re.compile(r'^\|\s{4,}File types:\s*(?P<rest>.+)$')
conf_re = re.compile(r'^\|\s{4,}Conf files:\s*(?P<rest>.*)$')
def parse_kv_counts(s):
out = {}
for tok in re.split(r"\s+", s.strip()):
m = re.match(r'(?P<key>[^()]+)\((?P<n>\d+)\)$', tok.strip(','))
if m:
out[m.group("key")] = int(m.group("n"))
return out
while i < len(lines):
line = lines[i].rstrip("\n")
if not line.strip():
i += 1; continue
if not line.startswith("|"):
break
m = win_re.match(line)
if m:
current_window = {"title": m.group("title"), "folders": []}
windows.append(current_window)
current_folder = None
i += 1; continue
m = fol_re.match(line)
if m and current_window is not None:
current_folder = {
"name": m.group("name"),
"file_count": int(m.group("count")),
"file_types": {},
"conf_files": []
}
current_window["folders"].append(current_folder)
i += 1; continue
m = types_re.match(line)
if m and current_folder is not None:
current_folder["file_types"] = parse_kv_counts(m.group("rest"))
i += 1; continue
m = conf_re.match(line)
if m and current_folder is not None:
rest = m.group("rest").strip()
current_folder["conf_files"] = [s.strip() for s in re.split(r"\s+", rest) if s.strip()] if rest else []
i += 1; continue
i += 1
return {"windows": windows}, i
def postprocess(headers):
h = dict(headers)
m = re.match(r"^(?P<label>.+?)\s+(?P{ver}[0-9.]+)\s*\((?P<meta>.+)\)$".replace("{","<").replace("}",">"), h.get("Version",""))
if m:
commit, btime = None, None
for p in (s.strip() for s in m.group("meta").split(",")):
if re.fullmatch(r"[0-9a-f]{7,}", p): commit = p
elif re.search(r"\d{4}-\d{2}-\d{2}T", p): btime = p
h["Version"] = {"name": m.group("label"), "version": m.group("ver"), "commit": commit, "build_time": btime, "raw": headers.get("Version","")}
m = re.match(r"^(?P<os>[A-Za-z_]+)\s*(?P<arch>x64|arm64)?\s*(?P<ver>[\w\.\-]+)?$", h.get("OS Version",""))
if m:
h["OS Version"] = {"os": m.group("os"), "arch": m.group("arch"), "version": m.group("ver"), "raw": headers.get("OS Version","")}
m = re.match(r"^(?P<model>.+?)\s*\((?P<count>\d+)\s*x\s*(?P<speed>\d+)\)$", h.get("CPUs",""))
if m:
h["CPUs"] = {"model": m.group("model").strip(), "count": int(m.group("count")), "speed_mhz": int(m.group("speed")), "raw": headers.get("CPUs","")}
m = re.match(r"^(?P<total>[\d\.]+)GB\s*\((?P<free>[\d\.]+)GB free\)$", h.get("Memory (System)",""))
if m:
h["Memory (System)"] = {"total_gb": float(m.group("total")), "free_gb": float(m.group("free")), "raw": headers.get("Memory (System)","")}
if isinstance(h.get("Process Argv"), str) and h["Process Argv"]:
h["Process Argv"] = [a for a in re.split(r"\s+", h["Process Argv"].strip()) if a]
return h
def parse_code_status_to_json(text: str) -> Dict:
lines = text.splitlines()
headers, idx = parse_headers(lines, 0)
processes, idx = parse_process_table(lines, idx)
workspace, _ = parse_workspace_stats(lines, idx)
return {
"headers": postprocess(headers),
"processes": processes,
"workspace_stats": workspace,
"raw": text
}
# ---------- Enumerating VS Code windows ----------
def _get_window_text(hwnd: int) -> str:
length = GetWindowTextLengthW(hwnd)
if length == 0:
# Sometimes the title is empty/zero
buf = ctypes.create_unicode_buffer(512)
GetWindowTextW(hwnd, buf, 512)
return buf.value
buf = ctypes.create_unicode_buffer(length + 1)
GetWindowTextW(hwnd, buf, length + 1)
return buf.value
def _get_window_class(hwnd: int) -> str:
buf = ctypes.create_unicode_buffer(256)
GetClassNameW(hwnd, buf, 256)
return buf.value
def _get_window_pid(hwnd: int) -> int:
pid = wintypes.DWORD()
GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
return int(pid.value)
def _is_vscode_window(hwnd: int) -> bool:
if not IsWindowVisible(hwnd):
return False
cls = _get_window_class(hwnd)
if cls not in VCODE_WINDOW_CLASS_CANDIDATES:
# Eliminate non-VS Code Electron/Chromium windows
return False
title = _get_window_text(hwnd)
if not title:
return False
# Accept if the title looks like VS Code
return any(k in title for k in VCODE_TITLE_KEYWORDS)
def enum_vscode_windows() -> List[int]:
hwnds: List[int] = []
def _callback(hwnd, lparam):
try:
if _is_vscode_window(hwnd):
hwnds.append(hwnd)
except Exception:
pass
return True
EnumWindows(EnumWindowsProc(_callback), 0)
return hwnds
# ---------- Core logic ----------
def extract_workspace_titles(status_json: Dict) -> List[str]:
"""Return the array of window titles listed in Workspace Stats."""
ws = status_json.get("workspace_stats") or {}
wins = ws.get("windows") or []
titles = []
for w in wins:
t = (w.get("title") or "").strip()
if t:
titles.append(t)
return titles
def choose_default_workspace(hwnds: List[int], non_default_titles: List[str]) -> Optional[int]:
"""
We treat "listed in Workspace Stats" as non-default.
A VS Code window whose title is **not** in that list is considered the default candidate.
"""
non_default_set = {t.strip() for t in non_default_titles}
for hwnd in hwnds:
title = _get_window_text(hwnd).strip()
if title and title not in non_default_set:
return hwnd
return None
def close_other_vscode_windows(keep_hwnd: Optional[int], hwnds: List[int]) -> List[int]:
"""Send WM_CLOSE to all except keep_hwnd. Return the HWNDs we attempted to close."""
closed = []
for h in hwnds:
if keep_hwnd is not None and h == keep_hwnd:
continue
# Send only (may not close immediately)
SendMessageW(h, WM_CLOSE, 0, 0)
closed.append(h)
return closed
def start_vscode_empty_window():
"""
Launch VS Code with an empty window.
- Try 'code --new-window' first
- If that fails, use Code.exe + cli.js with --new-window
"""
# 1) code command
# try:
# subprocess.Popen(["code", "--new-window"])
# return
# except Exception:
# pass
# 2) Code.exe + cli.js
pf = r"C:\Program Files"
pf86 = r"C:\Program Files (x86)"
for base in (os.path.join(pf, "Microsoft VS Code"),
os.path.join(pf86, "Microsoft VS Code")):
code_exe = os.path.join(base, "Code.exe")
cli_js = os.path.join(base, "resources", "app", "out", "cli.js")
if os.path.exists(code_exe) and os.path.exists(cli_js):
env = os.environ.copy()
env["VSCODE_DEV"] = ""
env["ELECTRON_RUN_AS_NODE"] = "1"
subprocess.Popen([code_exe, cli_js, "--new-window"], env=env)
return
raise RuntimeError("Failed to launch VS Code (Code.exe/cli.js not found).")
def main():
# 1) Get and parse `code --status`
status_text = read_code_status_text()
status_json = parse_code_status_to_json(status_text)
ws_titles = extract_workspace_titles(status_json)
# 2) Enumerate current VS Code windows
hwnds = enum_vscode_windows()
# 3) Choose the default workspace candidate
keep = choose_default_workspace(hwnds, ws_titles)
# 4) Default behavior
if keep is not None:
closed = close_other_vscode_windows(keep, hwnds)
print(json.dumps({
"action": "kept_default_window_and_closed_others",
"kept_hwnd": keep,
"closed_hwnds": closed,
"kept_title": _get_window_text(keep),
"workspace_titles_from_status": ws_titles
}, ensure_ascii=False, indent=2))
else:
# No default exists → create a new one
start_vscode_empty_window()
# Close all existing ones (per requirement: "keep one and close the rest" — close those existing before the new one)
closed = close_other_vscode_windows(None, hwnds)
print(json.dumps({
"action": "launched_new_default_window_and_closed_all_existing",
"closed_hwnds": closed,
"workspace_titles_from_status": ws_titles
}, ensure_ascii=False, indent=2))
if __name__ == "__main__":
try:
main()
except Exception as e:
print(json.dumps({"error": str(e)}, ensure_ascii=False))
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment