deco docs
GitHub

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.

tab in a two-space file, then in a four-space one

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.

The defaults, then whitespace everywhere, then rulers and interval line numbers

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.cursorStyle is 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-thin and block-outline collapse onto line and block. DECSCUSR has 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.

Switching from the dark theme to the light one

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.

Edit this page on GitHub