Configuration
deco reads VS Code’s own file formats, with VS Code’s own key names. That is the
whole point: an existing settings.json and keybindings.json should mean the
same thing here, and it is a constraint on every new feature rather than a shim
bolted on at the edges.
Where files are read from
deco keeps everything under one root, and falls back to VS Code’s location so an existing setup works without being copied.
| Platform | deco | VS Code (read-only fallback) |
|---|---|---|
| Linux / BSD | $XDG_CONFIG_HOME/deco, else ~/.config/deco |
~/.config/Code/User |
| macOS | ~/Library/Application Support/deco |
~/Library/Application Support/Code/User |
| Windows | %APPDATA%\deco |
%APPDATA%\Code\User |
Under that root: settings.json, keybindings.json, extensions/, snippets/.
VS Code splits these — user JSON under Code/User but extensions under
~/.vscode/extensions on every platform — and deco applies that quirk when
reading VS Code’s, while keeping its own together.
Nothing is ever written back to VS Code’s directory. The fallback is one-way.
settings.json
JSONC: comments and trailing commas are accepted, because VS Code’s own default settings file contains both and refusing to parse it would make the compatibility claim hollow.
Layers apply in VS Code’s order, each overriding the one before:
Default < User < Remote < Workspace < Folder
Remote is filled only in a remote session, from machine-settings.json on
the machine you connected to — settings that are facts about that machine rather
than about you. It is fetched over the connection, since this machine cannot
read it, and it is not trusted: see
Settings that belong to the machine.
Language-specific overrides work as they do in VS Code, and are resolved against the open document’s language:
{
"editor.tabSize": 4,
"editor.insertSpaces": true,
"files.insertFinalNewline": true,
"[rust]": {
"editor.tabSize": 4
},
"[makefile]": {
"editor.insertSpaces": false
}
}
Settings deco resolves into an open document’s behaviour: editor.tabSize,
editor.insertSpaces, editor.detectIndentation, editor.wordSeparators,
editor.wordWrap, editor.wordWrapColumn, editor.wrappingIndent,
editor.autoClosingBrackets, editor.autoIndent, editor.trimAutoWhitespace,
files.autoSave, files.autoSaveDelay, editor.renderControlCharacters,
editor.lineNumbers,
editor.renderWhitespace, editor.cursorStyle,
editor.cursorSurroundingLines, editor.scrollBeyondLastLine,
editor.rulers, editor.fontFamily, editor.fontSize, editor.lineHeight,
workbench.colorTheme, files.eol, files.trimTrailingWhitespace,
files.insertFinalNewline, plus extensions.* for the host and deco’s own
deco.lsp.* (see Language servers) and
deco.extensions.* (below).
files.eol is the ending a new file gets, as VS Code documents it. A file
that already has an ending keeps it, whether or not the setting agrees: opening
a CRLF file under "files.eol": "\n" does not stage a rewrite of every line in
it. The setting decides for an untitled buffer, and for a file with no
terminator in it at all — the two cases with no ending to keep. auto, the
default, falls back to the platform’s.
Settings a workspace cannot set
Most settings can be resolved by precedence and forgotten about: the
highest-priority layer wins and where it came from stops mattering. Three cannot,
because acting on them means deciding how much authority to hand executing
code, and a .vscode/settings.json arrives with a cloned repository:
| Key | Default | What it does |
|---|---|---|
deco.extensions.sandbox |
"container" |
"container" runs the extension host in a container; "process" runs it as an ordinary child process. |
deco.extensions.containerRuntime |
first of podman, docker found |
"podman", "docker", or an absolute path to one. Nothing else is accepted. |
deco.extensions.containerImage |
a digest-pinned Node image | The image the host runs in. Must be pinned as name@sha256:<64 hex>. |
These are read from deco’s own defaults and your user settings only. Workspace,
folder and remote layers are ignored for them, and the attempt is reported rather
than dropped in silence. The same rule already applies to deco.lsp.servers for
the same reason: see Language servers.
If the sandbox is "container" and no runtime can be found, deco refuses to
start the extension host and names this setting in the error. It does not fall
back. Extensions has the reasoning.
Four keys deco ships a default for are still read by nothing:
editor.tabCompletion, editor.largeFileOptimizations, files.encoding and
workbench.editor.enablePreview. Naming
them here because shipping a default is a claim: an unknown key deco never
mentioned is one thing, and one in deco’s own default settings file is another.
editor.fontFamily, editor.fontSize and editor.lineHeight are the GPU
frontend’s alone — a terminal has no font size — and the GPU frontend does not wrap
or draw whitespace, so editor.wordWrap, editor.wrappingIndent,
editor.renderWhitespace, editor.rulers and editor.lineNumbers: "interval" are
the terminal’s alone in the other
direction. The top-level README tracks
what is unbuilt.
Unknown keys are kept rather than rejected. A settings file written for VS Code contains a great many of them, and failing on the first one would make the file unusable.
The file outranks your indentation setting
editor.detectIndentation is on by default, in VS Code and here. It is why
opening somebody else’s two-space project and pressing tab does not reindent it
to four: your editor.tabSize is a preference for files that have no answer of
their own, and a file that has one outranks it.
The status bar says what one tab inserts, because nothing in the text does, and
it is what decides whether a diff is one line or forty. (detected) is added only
when the file’s indentation differed from your settings and won — a two-space
file read as two-space where editor.tabSize already said two overrode nothing,
and a permanent note about that would be noise on most files.
Two halves, guessed separately:
- Tabs or spaces is a vote: how many indented lines begin with a tab against how many begin with a space. An even split says nothing and the setting stands, because a mixed file is usually one halfway through being converted and guessing would finish the conversion in whichever direction the coin landed.
- How wide comes from the differences between consecutive lines’ indents, not from the indents themselves. A file indented by four has lines starting at 0, 4, 8 and 12 columns, and every one of those is a multiple of two — counting multiples would call it a two-space file. The differences are all four. Ties go to the smaller width, in VS Code’s own order.
A tab-indented file settles tabs or spaces and says nothing about how wide to
draw a tab, so editor.tabSize still decides that. Only the first
10,000 lines are examined, which is VS Code’s limit too: a file’s indentation is
evident long before that, and a generated file of half a million lines is not worth
scanning to be told the same thing.
Set "editor.detectIndentation": false to have your settings win outright. It can
arrive in workspace settings after the file is open and still takes effect — the
file’s answer is remembered rather than re-read, so nothing is copied to apply it.
What the view settings draw
Three settings change how the text area looks rather than how it behaves.
| Setting | Values | What deco does |
|---|---|---|
editor.renderWhitespace |
none, selection (default), boundary, trailing, all |
· per space, → per tab, in editorWhitespace.foreground |
editor.rulers |
a list of columns | Tints that column, in editorRuler.foreground |
editor.lineNumbers |
on (default), off, relative, interval |
interval numbers every tenth line, and the caret’s |
editor.cursorStyle |
line (default), block, underline, and the -thin / -outline variants |
Sets the terminal’s caret shape |
Whitespace. selection is VS Code’s default and the least intrusive useful
mode: the dots appear exactly where you are looking. boundary marks everything
except a single space with a word on each side — without that exception it would
be the same as all and would put a dot between every word of a sentence. A tab is
one arrow at the column it starts on and blank for the rest of its span, the way VS
Code draws it; filling the span with dots would make one tab indistinguishable from
the spaces it replaces.
Rulers are a hairline between two columns in VS Code, and a terminal has no space between cells to put one in. So a ruler becomes a tint of the cell instead, at a quarter strength — strong enough to follow down the screen, weak enough to read the code sitting on it, which is the column a ruler is there to warn about in the first place. A selection or a find match wins over it: those are what you are doing, and a ruler is furniture.
The caret shape is set through DECSCUSR, the escape sequence terminals use for
it, and restored when deco exits — leaving your shell with an editor’s caret would
not be deco’s business. Two details:
- Nothing is sent unless
editor.cursorStyleis written down. Your terminal’s caret is already configured, and replacing it with VS Code’s default on behalf of somebody who never mentioned it would be deco overruling a preference it was not asked about. Setting the key — even to"line", its default value — is asking. line-thinandblock-outlinecollapse ontolineandblock.DECSCUSRhas a bar, a block and an underline, and no thin or hollow variant of any of them, so the nearest shape is closer than refusing.
editor.cursorBlinking is not read, and the caret blinks: that is VS Code’s default
for it, and with the setting unread there is one answer rather than a choice.
Saving on a delay
files.autoSave: "afterDelay" writes the file files.autoSaveDelay milliseconds
after the last edit. It is off by default, in VS Code and here: an editor that
writes without being asked is a decision, not a convenience, so it stays one you make.
The clock restarts on every edit, so a delay measured from the first keystroke of a paragraph cannot fire in the middle of typing it. The save happens on an idle poll — the same one that lets a language server’s diagnostics arrive — so keys still coming in postpone it rather than racing it. A clean document is never written: an idle editor rewriting the same bytes every second would keep touching a modification time other tools watch.
files.autoSaveDelay is clamped to at least 100 ms. Zero would be a write per
keystroke, which is the thing the delay exists to avoid.
onFocusChange and onWindowChange are not honoured, and deco says so rather
than doing nothing: setting either puts a line in the problem list the editor shows at
startup, where an unknown colour theme already goes. Both need a focus event — an
editor losing focus is a tab switch, a window losing it is a terminal event not every
terminal sends — and a save that silently never happens is the worst way to find that
out.
keybindings.json
The same format, including chords, when clauses, per-platform mac keys, and
-command removals:
[
{ "key": "ctrl+alt+n", "command": "editor.action.insertLineAfter",
"when": "editorTextFocus && !editorReadonly" },
// Take a default away.
{ "key": "ctrl+k ctrl+d", "command": "-editor.action.moveSelectionToNextFindMatch" }
]
Later rules win, as they do in VS Code, so a user binding overrides a default with
the same key and when clause. Context keys are VS Code’s, verbatim —
editorTextFocus, textInputFocus, editorHasSelection,
editorHasMultipleSelections, suggestWidgetVisible, findWidgetVisible,
findInputFocussed, editorHasDiagnostics, editorHasDefinitionProvider,
editorHasDocumentFormattingProvider, isMac, isWindows — so a when clause
copied out of an existing file means the same thing.
A broken keybindings.json does not stop the editor from opening. Each entry that
fails to parse is reported through the session’s problem list and skipped: an
editor that refuses to start because of a typo in a config file cannot be used to
fix that typo.
Colour themes
workbench.colorTheme names a theme. Two are built in — Default Dark Modern and
Default Light Modern — and a theme extension from the marketplace works as-is,
because a theme is declarative and starts no host process.
ctrl+k ctrl+t switches between them.
The right-hand column is dark, light or high contrast, from the
contribution’s uiTheme. It is the part of the choice a label often does not say,
and it is what tells you whether the screen is about to go white.
The list is the two built-in themes — first, because they are the ones that always
work — followed by every contributes.themes entry of every extension under deco’s
extensions directory and VS Code’s, so a theme you installed for VS Code is
offered here without being copied. One label is offered once; the same extension
installed under two versions is the usual reason for a duplicate. Nothing is read
while listing: a picker over forty themes would otherwise parse forty files and
thirty-nine of them for nothing.
The choice lasts the session. Making it stick means putting
workbench.colorTheme in your settings, which the status bar says when the theme
changes.
deco does not write settings files, and that is the decision rather than a gap.
It reads them and never writes them, so your settings.json is a file you own: what
is in it is what you put there, comments and formatting included, and nothing
appears in it because of a key you pressed.
The cost is real and worth stating. VS Code writes workbench.colorTheme when you
pick a theme and is not thought rude for it, so this is a divergence you may not
want: a theme chosen with ctrl+k ctrl+t has to be written down by hand to
survive. The status bar says which line to add, and that is the whole of the
mechanism.
The same answer settles a question it is easy to reach from the other direction: there is no per-workspace “I trust this repository” for a workspace-defined language server, because remembering that answer would mean writing it somewhere.
A theme that cannot be read reports why and leaves the current one alone, because the alternative is an editor with no colours.
What is read from a theme file: colors, tokenColors (including TextMate scope
matching), semanticTokenColors, and include chains for themes that build on
another. Naming a theme deco cannot find falls back to the dark theme and says so
rather than starting with no colours.
.tmTheme (plist) themes are not supported, and neither are - scope
exclusions in a scope selector.
The terminal frontend composites translucent colours — selections, find highlights — against the editor background, because a terminal cell has no alpha.
Nothing here fails closed
Every configuration path degrades rather than refusing: an unknown theme falls back, a broken keybinding is skipped, an unparseable workspace settings file is reported and ignored, an unknown setting is kept. The editor collects what went wrong into a list the frontend can show. This is deliberate — configuration is exactly the thing you need a working editor to repair.