The Spin2 Formatter automatically formats your .spin2 source files: normalizing indentation, aligning columns, adjusting keyword case, and cleaning up whitespace. It is designed to produce clean, readable code that compiles to the exact same binary as the original — formatting never changes what your code does.
All formatter settings are in the VSCode Settings UI:
- Open Settings: File > Preferences > Settings (or
Ctrl+,/Cmd+,) - In the search bar, type "Spin2 Formatter"
- All formatter options appear in the Spin2 Document Formatter section
The formatter is disabled by default. To enable it:
- Open Settings and search for "Spin2 Formatter"
- Check the box for Enable — "Enable the Spin2 document formatter"
To automatically format every time you save a .spin2 file:
- Check the box for Format On Save — "Automatically format Spin2 documents when saving"
Both Enable and Format On Save must be checked for auto-formatting to work.
With the formatter enabled, you can format manually:
- Keyboard:
Shift+Alt+F(Windows/Linux) orShift+Option+F(Mac) - Command Palette:
Format Document - Right-click:
Format Documentfrom the context menu
When a .spin2 file is open, the status bar shows the active whitespace mode:
- "Spin2 Spaces: 2" — using 2-space indentation
- "Spin2 Tabs: 8" — using tab characters (width 8)
- "Spin2 Prop Tool" — using PropellerTool elastic tabstops
- "Spin2 IronSheep" — using IronSheep elastic tabstops
Click the indicator to switch between spaces, tabs, or any elastic tabstop profile from a single menu. You can also change the indent size and tab width from this menu.
All settings are in the Spin2 Document Formatter section of the Settings UI. Search for "Spin2 Formatter" to find them.
| Setting | Default | Description |
|---|---|---|
| Tabs To Spaces | checked | Convert tab characters to spaces. Uncheck to keep tab characters in the output. |
| Tab Width | 8 |
Width of a tab character for conversion and alignment calculations. |
| Trim Trailing Whitespace | checked | Remove trailing spaces and tabs from all lines (except inside block comments). |
| Insert Final Newline | checked | Ensure the file ends with exactly one newline character. |
| Setting | Default | Options |
|---|---|---|
| Indent Size | 2 |
2, 4, or 8 |
This controls the number of spaces per indent level inside PUB and PRI method bodies. Code nesting is preserved; only the width of each level changes.
With indent size 2 (default):
PUB main() | i
repeat i from 0 to 9
if i > 5
debug("big")
With indent size 4:
PUB main() | i
repeat i from 0 to 9
if i > 5
debug("big")
These three settings are independent — each controls a different location in the file and they do not interact with each other:
| Setting | Default | Scope |
|---|---|---|
| Max Consecutive Blank Lines | 1 |
Controls blank lines within a section body (e.g., inside a PUB method or a CON block). |
| Blank Lines Between Sections | 1 |
Controls blank lines at boundaries between different sections (e.g., CON→VAR, DAT→PUB). |
| Blank Lines Between Methods | 2 |
Controls blank lines between consecutive PUB/PRI methods. |
For example, the defaults give you at most 1 blank line inside a method body, 1 blank line between a CON and VAR section, but 2 blank lines between consecutive PUB or PRI methods — all at the same time without conflict.
| Setting | Default | Description |
|---|---|---|
| Space After Comment Start | checked | Insert a space after ' or '' in comments. |
When enabled:
'textbecomes' text''textbecomes'' text- Trailing comments get exactly 2 spaces between code and the
'marker
The formatter provides six independent case controls. Each appears as a dropdown in the Settings UI with three options:
| Dropdown Value | Effect |
|---|---|
| uppercase | Force to UPPERCASE |
| lowercase | Force to lowercase |
| preserve | Leave as-is (no changes) |
Setting: Block Name Case (default: uppercase)
Controls the case of section keywords: CON, VAR, OBJ, DAT, PUB, PRI.
' With "uppercase" (default): ' With "lowercase":
CON con
VAR var
PUB main() pub main()
Setting: Control Flow Case (default: preserve)
Controls keywords used for program flow in PUB/PRI methods:
if, ifnot, elseif, elseifnot, else, case, case_fast, other, repeat, from, to, step, while, until, with, next, quit, return, abort
' With "preserve" (default): ' With "lowercase":
Repeat i From 0 To 9 repeat i from 0 to 9
If i > 5 if i > 5
Quit quit
Setting: Method Case (default: preserve)
Controls built-in methods and constants:
- Methods:
cogspin,coginit,cogstop,cogid,pinwrite,pinread,pinlow,pinhigh,pinfloat,pintoggle,locknew,lockret,locktry,lockrel,debug,send,recv,waitct,pollct,getct,wrpin,wxpin,wypin,rdpin,rqpin,akpin, and many more - Constants:
true,false,clkfreq,clkmode,pi,negx,posx
' With "preserve" (default): ' With "lowercase":
PinWrite(PIN, 1) pinwrite(PIN, 1)
if x == True if x == true
Debug("value: ", udec(x)) debug("value: ", udec(x))
Setting: Type Case (default: uppercase)
Controls type keywords: BYTE, WORD, LONG, STRUCT
These appear in VAR blocks, DAT blocks, and method bodies:
' With "uppercase" (default): ' With "lowercase":
VAR VAR
LONG position long position
BYTE flags[8] byte flags[8]
Setting: Constant Case (default: uppercase)
Controls the case of user-defined constant names from CON sections. The formatter collects all constant names you define in CON blocks and normalizes their case everywhere they appear.
' With "uppercase" (default): ' With "preserve":
CON CON
MAX_SERVOS = 6 Max_Servos = 6
PUB main() PUB main()
if count > MAX_SERVOS if count > Max_Servos
Setting: Pasm Instruction Case (default: preserve)
Controls PASM assembly instruction mnemonics in DAT sections and inline PASM:
mov, add, sub, jmp, call, ret, org, end, wrlong, rdlong, and all other P2 PASM instructions.
' With "preserve" (default): ' With "uppercase":
DAT DAT
org ORG
myLabel mov x, #5 myLabel MOV x, #5
add x, y ADD x, y
end END
Default (section keywords and types uppercase, everything else left alone):
- Block Name Case:
uppercase - Control Flow Case:
preserve - Method Case:
preserve - Type Case:
uppercase - Constant Case:
uppercase - Pasm Instruction Case:
preserve
All uppercase:
Set all six dropdowns to uppercase.
Hands-off (only do whitespace/alignment, don't change any case):
Set all six dropdowns to preserve.
The formatter aligns constant assignments vertically:
' Before: ' After:
CON CON
MAX_SERVOS=6 MAX_SERVOS = 6
DEFAULT_POS = 1500 DEFAULT_POS = 1500
PIN_LED= 56 PIN_LED = 56
- Names are indented consistently
=signs are vertically aligned- Values are placed immediately after
= - Interspersed comments align to the same indent as the constants
Enum groups (lines using #) are normalized to single-space-after-comma:
CON
#0, STATE_IDLE, STATE_RUN, STATE_STOP
' Before: ' After:
VAR VAR
LONG position LONG position
BYTE flags[ 8 ] BYTE flags[8]
WORD reading WORD reading
- Types aligned at a consistent column
- Names aligned at a consistent column
- Comma spacing normalized
' Before: ' After:
OBJ OBJ
servo:"servo_driver" servo : "servo_driver"
display : "ssd1306" display : "ssd1306"
segments[7] : "segment_drv" segments[7] : "segment_drv"
- Object names aligned (including array declarations like
segments[7]) - Colons vertically aligned
- Filenames aligned
Data-only DAT sections indent labels:
DAT
servoIdx long 0
servoOffset long 1500
msgLOW byte "LO", 0
DAT sections with PASM use column 0 for labels and align all 6 PASM columns (label, condition, mnemonic, operands, effects, comment) independently within each ORG...END region. Data declarations and PASM instructions have separate column alignment — long data label names don't push instruction mnemonics wider:
DAT
org
' data declarations align independently
maskQtrRowsModulus long 0
redBitRGB1Value long $01
' instructions use their own columns
mov pa, #1 ' load immediate
if_z jmp #done ' branch if zero
done ret ' return
end
The formatter detects your code's nesting structure and re-expresses it using the configured indent size:
' Before (messy, inconsistent): ' After (indentSize: 2):
PUB main() | i PUB main() | i
repeat i from 0 to 9 repeat i from 0 to 9
if i > 5 if i > 5
debug("big") debug("big")
else else
debug("small") debug("small")
Inline PASM blocks (ORG...END within methods) are formatted using DAT/PASM alignment rules.
In all sections, trailing comments are vertically aligned within their block:
CON
MAX_SPEED = 100 ' maximum motor speed
MIN_SPEED = 10 ' minimum motor speed
ACCEL_RATE = 5 ' acceleration step
The formatter is careful to preserve certain content exactly as-is:
-
Block comments (
{ }and{{ }}): Everything inside block comments is untouched — including'comment lines within{ }blocks. Use block comments for ASCII art, tables, commented-out code, or any content that relies on exact spacing. -
Column-0 comments: Comments that start at column 0 (
'...or''...) are never moved. These are typically file headers, section banners, and dividers. -
String literals: Content inside
"..."is never modified. Keyword case normalization skips string content to avoid changing compiled output. -
debug() arguments: Content inside
debug(...)calls is not case-normalized. The Spin2 compiler treats type keywords inside debug differently, so changing case there can change behavior. -
Backtick expressions: Debug display widget specifications (
`scope,`bitmap, etc.) are preserved as-is. -
Preprocessor directives: Lines starting with
#define,#ifdef,#ifndef,#else,#endif,#include,#undef,#pragma,#error, or#warnare left completely untouched — no indentation, no case changes, no alignment.
If you have elastic tabstops enabled (check Enable under the Spin2 Elastic Tabstops section in Settings), the formatter uses your custom tabstop positions instead of the built-in defaults. This lets you control exactly where columns align in each section.
The formatter reads tabstop arrays from your selected profile (PropellerTool, IronSheep, or User1). If a section doesn't have a custom tabstop array, it falls back to the PropellerTool defaults.
The status bar indicator shows the active profile name — "Spin2 Prop Tool", "Spin2 IronSheep", or "Spin2 User1" — instead of a generic label. Click it to switch between spaces, tabs, or any elastic tabstop profile from a single menu.
Note: Method indentation (indent size x nesting level) is not affected by elastic tabstops. Elastic tabstops only affect column alignment in CON, VAR, OBJ, DAT, and trailing comment positioning in PUB/PRI.
- Start with defaults: Enable the formatter with default settings first. Adjust individual settings as you discover preferences.
- Use format-on-save: Once you're comfortable with the formatter's output, enable Format On Save to keep code consistent without thinking about it.
- Block comments for art: If you have carefully formatted tables, diagrams, or ASCII art, wrap them in
{ }block comments to protect them from reformatting. - Preserve mode: Set all six case dropdowns to
preserveif you only want whitespace/alignment formatting without any keyword case changes. - The formatter is safe: Formatting never changes what your code compiles to. The formatter is tested by compiling before and after formatting and verifying the binary output is identical.
For users who prefer editing settings.json directly (open via Command Palette: Ctrl+Shift+P > "Preferences: Open User Settings (JSON)"), here are all formatter settings with their default values:
{
"spinExtension.formatter.enable": false,
"spinExtension.formatter.formatOnSave": false,
"spinExtension.formatter.trimTrailingWhitespace": true,
"spinExtension.formatter.insertFinalNewline": true,
"spinExtension.formatter.maxConsecutiveBlankLines": 1,
"spinExtension.formatter.blankLinesBetweenSections": 1,
"spinExtension.formatter.blankLinesBetweenMethods": 2,
"spinExtension.formatter.tabsToSpaces": true,
"spinExtension.formatter.tabWidth": 8,
"spinExtension.formatter.indentSize": 2,
"spinExtension.formatter.blockNameCase": "uppercase",
"spinExtension.formatter.controlFlowCase": "preserve",
"spinExtension.formatter.methodCase": "preserve",
"spinExtension.formatter.typeCase": "uppercase",
"spinExtension.formatter.constantCase": "uppercase",
"spinExtension.formatter.pasmInstructionCase": "preserve",
"spinExtension.formatter.spaceAfterCommentStart": true
}You only need to include settings where you want a non-default value. For example, to enable the formatter with 4-space indentation and lowercase control flow:
{
"spinExtension.formatter.enable": true,
"spinExtension.formatter.formatOnSave": true,
"spinExtension.formatter.indentSize": 4,
"spinExtension.formatter.controlFlowCase": "lowercase"
}