Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>MARS Developer ToolsNew to Visual Studio Code? Get it now.
MARS Developer Tools

MARS Developer Tools

STZ Robotics

|
2 installs
| (0) | Free
MARS framework support for FRC robot projects.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

MARS for VS Code

The editor half of MARS. It reads a robot project the way the framework does — subsystems, their IO layers, their requests, the NetworkTables topics they publish — and puts that where the code is, instead of leaving it to be reconstructed by opening six files.

It lives here rather than in its own repository because it talks to things this repository defines: the vendordep in lib/, the project layout in templates/project/, the workspace-mars/ folder MARS Desktop writes. A version of it that disagrees with those is a version that breaks somebody's project. In here, a change to both is one commit.

What it does

A sidebar. Two views. Project answers what a newcomer asks before touching anything — is the dashboard installed, what team is this, which run mode is the Manifest pinned to, which MARS am I compiling against, what packages are installed — and every answer is a link to the file that decides it. Its title bar has the buttons that launch MARS Desktop and Simulation Studio.

Architecture. MARS spreads a subsystem across files on purpose: the data here, the IO interface there, three implementations of it, one class per request. That is what makes each piece swappable, and it is also why nobody can see a subsystem whole. This view puts the pieces back together without moving any of them, and every node jumps to its declaration.

CodeLens. The same links, over the declarations themselves. Above a subsystem: its data class, its IO interface and how many implementations it has, its requests, and the NetworkTables table it publishes under. Above a @Tunable or @Signal: the full topic it produces, which is otherwise the annotation's key, the member's name and the subsystem's key somewhere else entirely. Above a request: the subsystem it acts on — a link nothing else can follow, because a request names only the pair of types and never the subsystem.

Live NetworkTables. Connect to the robot or the simulator and the value of every topic appears at the end of the line that publishes it — → 0.823 beside the @Tunable field, beside the NetworkIO.set, beside the @Signal. A lens over a @Tunable writes a new value back. The architecture tree shows the same values on its topic nodes.

It is not a second dashboard: no graphs, no history, no log playback. Those are what MARS Desktop is for, and an editor that grew them would be a worse version of it. Two things follow from keeping it small — it does not synchronise clocks (nothing here shows a timestamp, and a written value is stamped by the server on arrival), and it subscribes only to the tables this project publishes, which the scan already knows, instead of to the whole tree.

Diagnostics. Six things that compile, boot, and then go wrong quietly:

mars.duplicateKey Two subsystems claiming one NetworkTables table. They do not collide at construction; they collide on the wire.
mars.topicCollision One topic published from two places. The last write each loop is the one the dashboard shows.
mars.nullKey The subsystem wizard's .key(null) still in place. The subsystem publishes under no table at all.
mars.orphanSubsystem A subsystem no container ever mentions. Nothing constructs it, so it never runs.
mars.unimplementedIo An IO interface with nothing behind it, so Injector.createIO has nothing to hand out.
mars.runMode The Manifest pinned to anything but REAL. This is the one that costs a match.

Plus mars.featureRequirement, when an installed Feature declares a marsCoreRequired the vendordep does not satisfy — otherwise a Gradle failure on a method that does not exist yet, with nothing pointing at the package that wanted it.

Quick fixes, because a rule that only reports is a rule that gets turned off. .key(null) creates the KeyManager constant its own TODO names — or uses it, when the wizard already left it there — and imports it. An orphan subsystem gets constructed in the container, with the Injector form when all three IO implementations exist and the one that does exist otherwise, because a fix that does not compile is worse than none. An interface with nothing behind it gets Real and Sim written, with every method stubbed and a return where the signature needs one; never Fallback, which @Fallback generates at build time. And the Manifest goes back to REAL.

There is no fix for a topic collision. Picking which of the two is wrong would be a guess, and a quick fix that guesses gets accepted without being read.

Renaming a table. MARS: Rename a NetworkTables table rewrites the key constant and every literal that names the same table. It is a rename of strings, which is why no language server offers it and why doing it by hand always leaves one behind — and the one left behind keeps publishing under the old name with nothing reading it.

Snippets and hovers. Snippets for every MARS pattern, written to match what the subsystem wizard generates rather than an idealised version of it — so a hand-written subsystem and a generated one look the same. Hovers explain what a Request is for and what returning from it actually does, which is the part that does not fit in a Javadoc comment.

Completion where no language server can help. NetworkTables tables and keys are strings, and a typo in one produces a topic nobody ever sees. Inside NetworkIO.set(...) and .key(...) it offers the project's own key constants; in the key position it offers the names already published under that table.

A status bar entry carrying the run mode, because a project left in SIM looks exactly like a project in REAL until the robot is enabled.

Keys held in constants

The subsystem wizard writes .key(KeyManager.ARM_KEY), never a literal. The dashboard's own source map stops at that constant and says so. This extension does not have to: it has already read every file, so it resolves the constant and names the topic. That is why the architecture view shows /Arm/Position in a project where nothing says "Arm" anywhere near the subsystem.

What it still cannot resolve is a key built by concatenation or returned from a method. Those are reported as unresolved rather than guessed at, which is what mars.nonLiteralKey is for.

Settings

mars.desktopPath Full path to mars-desktop. Empty means look where the installer puts it, and in this repository's build output.
mars.simulationStudioPath The same for mars-sim-app.
mars.codeLens Turn the lenses off.
mars.networkTables.address Connect straight to this host:port. Empty means being offered the simulator, the robot derived from the team number, the Driver Station and Systemcore.

Working on it

cd extension
npm install
npm run watch       # esbuild + tsc, both in watch mode
npm run compile     # type-check, lint and bundle once
npm test            # both suites

Press F5 for an Extension Development Host with this extension loaded. Debugging needs the connor4312.esbuild-problem-matchers extension, which .vscode/extensions.json already recommends.

dist/extension.js is what ships, src/ is what you edit.

The two test suites

npm test runs both, and they need different things:

  • unit (src/test/unit/) drives the scanner with Java as strings. Every case in it is either a shape the subsystem wizard generates, or something that has already fooled the scanner once — a brace inside a string literal, a bounded type parameter (<D extends Data<D>>) read as a supertype.
  • integration (src/test/integration/) opens src/test/fixture/, a real MARS project deliberately left in several broken states, and checks that the extension wakes up for it, that the diagnostics land on the right lines of the right files, and that each quick fix produces the text it claims to. The fixture is also the fastest way to see the extension do anything: open that folder in the Development Host.

The quick-fix tests apply their edits for real and read the buffer back, then revert; nothing is ever saved, so the fixture is byte-identical before and after a run. The NetworkTables tests run against a WebSocket server that speaks the protocol (src/test/fakeNt.ts) rather than against a mocked socket, because the wire format is the part that can actually be wrong and a mock would only assert that the client calls its own methods.

The scanner is regex over Java, not a parser — the same choice source_map.rs makes in the dashboard, and for the same reasons. It means the tests are the only thing keeping it honest, so a bug fixed there should arrive with the case that found it.

Before it can be published

  • publisher is missing from package.json. It needs the Marketplace publisher id, which is an account thing and not a code thing.
  • The Marketplace wants a 128×128 PNG icon; media/mars.svg is the activity bar icon, which is a different thing and may stay an SVG.
  • The version is the extension's and follows nothing else here. The dashboard, the Studio and the library each have their own, and scripts/sync-version.mjs deliberately does not touch this one.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft