codingame_tools.language.vscode¶
vscode
¶
Generating VS Code run/debug configuration for a puzzle/contribution working directory.
A language plugin describes what it needs (CgLanguage.build_vscode_provisioning ->
CgVsCodeProvisioning); this module owns where it goes and how it merges with whatever the
user already has. Sibling of _process.py/_docker.py--deliberately outside languages/, so
the registry's discovery walk never sees it.
Two things here are less obvious than they look:
VS Code only reads .vscode/launch.json from the workspace root folder, never from a
subdirectory. A puzzle working directory is very often a subdirectory of the user's real
workspace (this repo's own puzzle/ and contribution/ are exactly that), so writing
<root>/.vscode/launch.json would be silently ignored. Everything therefore targets a
resolved workspace root (see find_workspace_root), and every generated path that refers to
the working directory is absolute--${workspaceFolder} is the wrong answer whenever the
workspace root isn't the working directory, which is the common case.
launch.json/tasks.json are JSONC, not JSON: VS Code allows // comments and trailing
commas, and plenty of real files use them. json.loads raises on those, so rather than risk
corrupting a hand-edited file this module refuses to merge into anything it can't parse strictly
(see CgVsCodeMergeError) unless explicitly told to overwrite.
Generated entries are owned by the language, not by the working directory. One entry per
language serves every puzzle and contribution in the workspace, because everything that used to
vary per directory is now resolved at launch time instead of baked in: which directory, from
VS Code's ${file}; which test, from that directory's .meta/selected-test.json. So provisioning
a second working directory replaces the first's entries rather than adding to them, and
launch.json never needs regenerating after an import, a language change, or a new directory.
Versions through 1.0.x named entries per directory ("CG puzzle: ...", input ids cg_puzzle_*)
and carried a pickString list of test cases that went stale the moment tests changed.
_OWNED_NAME_RE/_OWNED_INPUT_RE match both spellings, so those are cleaned out on the next
provisioning run rather than accumulating forever.
MANAGED_PREFIX
module-attribute
¶
MANAGED_PREFIX = 'CG '
Marks every launch.json configuration name and tasks.json label cg has ever generated, in
any version. Stable forever--changing it would strand every entry written by an older release.
Level one of a three-level name (see entry_name). Its job is to make cg's entries identifiable
as a set, so all of them can be swept in one go regardless of language or version--which is what
makes an unrecognized leftover recoverable rather than permanent clutter.
PRESENTATION
module-attribute
¶
PRESENTATION = {'group': 'cg'}
presentation for every generated launch configuration, which clusters cg's entries together in
VS Code's Run and Debug dropdown instead of scattering them among the user's.
Worth having because F5 runs whatever configuration is selected, never the one matching the
file you have open. Debugging a solution therefore means picking the CG <language>: ... entry
first, and the commonest way to get a solution that hangs at its first read is to press F5 with
someone's own "Python: Current File" still selected--that runs the solution directly, with stdin
attached to the terminal rather than to a test case. Grouping can't prevent that, but it makes
the right entry easy to find.
Deliberately does not set order to push cg above the user's own entries: their configurations
are theirs to rank.
ACTION_DEBUG
module-attribute
¶
ACTION_DEBUG = 'Debug solution'
Launch configuration: debug the solution the active editor tab belongs to, against that working directory's selected test case.
ACTION_PREPARE_DEBUG
module-attribute
¶
ACTION_PREPARE_DEBUG = 'Prepare debug session'
Task: get everything in place for a debug launch--build the debug profile, stage the selected
test case's input--and exit. A preLaunchTask.
Deliberately prepares rather than starts: the debugger launches the program itself, as it does for any ordinary local target. An earlier design had this task start a debug server for the adapter to attach to, which is what forced the program's output somewhere the editor could not see it.
CgVsCodeRequest
dataclass
¶
CgVsCodeRequest(ctx, workspace_root, debug_adapter_logging=False)
What a language plugin is being asked to generate configuration for.
Deliberately thin. It used to also carry the working directory's kind and its full list of
test cases, because a generated debug configuration had to name both; now ${file} and
.meta/selected-test.json answer those at launch time, so a plugin needs neither and the
configuration it produces is the same for every working directory in the workspace.
ctx
instance-attribute
¶
ctx
The CgLanguageContext for the working directory (typed loosely to avoid a circular import
with base--see codingame_tools.language.base.CgLanguageContext).
Present for the language's own needs (its toolchain paths, say), not so generated entries can bake in this directory--see the module docstring.
workspace_root
instance-attribute
¶
workspace_root
Where .vscode/ will be written--see find_workspace_root. Often not ctx.root.
Also the directory a containerized language must mount, so that paths inside the container
match the paths VS Code has open--see codingame_tools.language._docker.
debug_adapter_logging
class-attribute
instance-attribute
¶
debug_adapter_logging = False
Generate a configuration that logs the debug adapter's own conversation with the debugger.
Off by default because it is loud and slows a session down. It exists because the debug adapter is the one component of the stack that can't be exercised from a terminal: gdbserver, stdin redirection, stepping and symbol resolution can all be driven by hand and checked, but what VS Code's adapter actually sends and receives can only be observed from inside a real session. When a session misbehaves and everything underneath it demonstrably works, this is the remaining place to look.
A plugin should turn on whatever its adapter offers, and quieten anything so voluminous it would bury the exchange.
CgVsCodeProvisioning
dataclass
¶
CgVsCodeProvisioning(configurations=list(), inputs=list(), tasks=list(), files=dict(), retired_names=list(), obsolete_files=list(), recommended_extensions=list())
What a language plugin wants written. Everything is optional--a plugin supplies only the pieces it actually has.
configurations
class-attribute
instance-attribute
¶
configurations = field(default_factory=list)
Entries for launch.json's configurations. Each name must come from entry_name(), so
re-provisioning replaces exactly cg's own entries for this language and leaves everything
else--the user's, and other languages'--alone.
Must not bake in anything specific to request.ctx.root: one entry serves every working
directory in the workspace (see the module docstring).
inputs
class-attribute
instance-attribute
¶
inputs = field(default_factory=list)
Entries for launch.json's inputs. Each id must start with cg_ (see
_OWNED_INPUT_RE), for the same reason.
Nothing populates this today: the pickString test-case pickers it existed for are what the
.meta/-based selection replaced. Kept because it is a real launch.json capability a future
plugin may need, and because write_provisioning must go on pruning 1.0.x leftovers.
tasks
class-attribute
instance-attribute
¶
tasks = field(default_factory=list)
Entries for tasks.json's tasks. Each label must come from entry_name(), and the same
"nothing per-directory" rule applies.
files
class-attribute
instance-attribute
¶
files = field(default_factory=dict)
Extra files to write, keyed by path relative to the working directory root (not the
workspace root)--e.g. .meta/.devcontainer/devcontainer.json.
Belongs under .meta/. These files are generated and not the user's to maintain, and
.meta/ is the only part of a working directory that is gitignored, so anywhere else they'd
be committed into whatever repository tracks the directory. That also rules out data/
specifically, which for a contribution is a git work tree where a stray generated file would
be swept into the server tree by git add -A or deleted by git clean -fd.
retired_names
class-attribute
instance-attribute
¶
retired_names = field(default_factory=list)
Configuration names / task labels earlier versions of this plugin generated and no longer do, so they are removed rather than orphaned.
Mostly a backstop. _is_owned_by_this_run already replaces everything in this language's
namespace, so renaming an action is handled with no declaration at all. This is for the
rarer change that moves an entry out of that namespace--a language's cg_id changing, say--
where nothing else would connect the old name to the new one.
The files equivalent is obsolete_files. Both exist for the same reason: what cg generated
is cg's to clean up, and nothing else's.
obsolete_files
class-attribute
instance-attribute
¶
obsolete_files = field(default_factory=list)
Paths (relative to the working directory root, like files) that earlier versions of this
plugin generated and that should now be deleted.
Generated files are cg's to clean up: a user who upgrades shouldn't be left with a stale
devcontainer.json in a location nothing writes to any more, silently offering VS Code a
"Reopen in Container" that no longer reflects anything. The launch.json equivalent is
_OWNED_NAME_RE.
Only ever names specific files, never directories, and a containing directory is removed only if deleting the file leaves it empty--so a path the user has since put their own work in is left alone.
recommended_extensions
class-attribute
instance-attribute
¶
recommended_extensions = field(default_factory=list)
Extension IDs to merge into extensions.json's recommendations (union, never removing the
user's own--there's no reliable way to tell which ones cg previously added).
CgVsCodeMergeError
¶
Bases: Exception
Raised when an existing VS Code config file can't be safely merged into--almost always because it's JSONC (comments/trailing commas) rather than strict JSON. Refusing is deliberate: silently rewriting would drop the user's comments and any content our parser didn't understand.
find_workspace_root
¶
find_workspace_root(root)
Best guess at the VS Code workspace root folder that contains root.
Walks up from root looking for a directory that already has .vscode/, then for a VCS
marker (.git/.hg/.svn), stopping at the filesystem root. Falls back to root itself,
which is correct when the user opens the working directory directly as their folder.
This matters because VS Code reads launch.json only from the workspace root--see the
module docstring.
Source code in codingame_tools/language/vscode.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
entry_name
¶
entry_name(language, action)
The name of a generated launch.json configuration or tasks.json label.
Three levels, each doing a distinct job:
CG C++: Debug solution
└┬┘ └┬┘ └─────┬─────┘
│ │ └─ 3. a well-known action, from the ACTION_* constants below
│ └─────────── 2. the language, partitioning ownership
└─────────────── 1. MANAGED_PREFIX, marking the whole set as cg's
- Level 1 makes every cg entry, of any language and any version, identifiable as a set.
- Level 2 keeps languages independent. A provisioning run only ever generates for one language, so without this partition, provisioning a C++ directory would delete the Python entry in the same workspace--and whichever you provisioned last would be the only one that worked.
- Level 3 is drawn from a fixed vocabulary rather than free text, so an entry keeps its
identity across releases and re-provisioning replaces it instead of adding a second one.
When one genuinely has to change, the old spelling goes in
CgVsCodeProvisioning.retired_names.
Nothing here encodes a working directory. One entry per (language, action) serves the whole workspace--see the module docstring.
Source code in codingame_tools/language/vscode.py
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | |
write_provisioning
¶
write_provisioning(provisioning, *, root, workspace_root, language, force=False, dry_run=False)
Write provisioning into <workspace_root>/.vscode/ (and provisioning.files into
root), merging with whatever is already there.
Only files whose content would actually change are touched, so re-running when everything is already current is a no-op on disk--no diffs, no timestamps, no editor reload prompts.
Parameters:
-
provisioning(CgVsCodeProvisioning) –What the language plugin produced.
-
root(Path) –The working directory root. Only the base for
provisioning.filesnow--entries are owned by declared name, not by a directory. -
workspace_root(Path) –Where
.vscode/lives--seefind_workspace_root. -
language(str) –The
cg_idof the language being provisioned for. Scopes which existing entries this run owns--see_is_owned_by_this_run--so provisioning one language never disturbs another's. -
force(bool, default:False) –Overwrite an existing config file that isn't strict JSON instead of refusing.
-
dry_run(bool, default:False) –Work out what would change without touching anything. See
check_provisioning.
Returns:
-
list[Path]–Every path that changed (or, under
dry_run, would change), in write order. Empty means -
list[Path]–everything was already up to date.
Raises:
-
CgVsCodeMergeError–if an existing file can't be parsed and
forceis False.
Source code in codingame_tools/language/vscode.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 | |
check_provisioning
¶
check_provisioning(provisioning, *, root, workspace_root, language)
Every path write_provisioning would change, without changing anything.
This is how a user finds out their VS Code configuration has gone stale. Generated entries carry no version stamp and need none: the generated content is the version, so "would writing it change anything?" answers the question exactly, and keeps answering it correctly when a future release alters what gets generated.
force is deliberately absent. A config file that can't be parsed raises
CgVsCodeMergeError here just as it would on a real run, because "I would have to overwrite
your hand-edited file" is precisely what a check should report.
Source code in codingame_tools/language/vscode.py
511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | |