← All writing
Craft · · 11 min

Mostly off

Subtraction as a configuration strategy, plus the sediment at the bottom of the file.

Tooling

Every so often I share my screen and somebody stops me inside the first ten seconds to ask where my tabs went. Then, if they’re the sort of person who notices these things, a follow-up about the minimap, and then a slightly concerned question about the line height.

The honest summary of my settings.json is that it’s mostly a list of things I’ve turned off. I counted once and there are somewhere north of 70 entries in that file whose entire job is to make VS Code stop doing something it does by default. It is less a configuration than a series of small vendettas.

I don’t think that’s an accident of temperament, though it’s probably partly that. I think editor defaults are set by people optimizing for a first run, and a first run and a fourth year are extremely different problems.

The editor is a text buffer

Here’s the part that took me embarrassingly long to articulate. Every default in an editor is a decision somebody else made about what deserves your attention, and they made it without knowing anything about what you’re doing today.

Individually, each one is defensible. Indent guides help you see nesting. Bracket pair colorization helps you see scope. Occurrence highlighting helps you see the other places a symbol appears. Sticky scroll helps you remember which function you’re in. The lightbulb helps you find refactors. Sure, all true.

But they don’t arrive individually. They arrive all at once, permanently, in the same 900 pixels, and the aggregate is a dashboard where every warning light is lit all the time, which means none of them mean anything.

settings.json
"editor.bracketPairColorization.enabled": false,
"editor.colorDecorators": false,
"editor.guides.indentation": false,
"editor.lightbulb.enabled": "off",
"editor.matchBrackets": "never",
"editor.minimap.enabled": false,
"editor.occurrencesHighlight": "off",
"editor.renderLineHighlight": "none",
"editor.selectionHighlight": false,
"editor.stickyScroll.enabled": false,
"breadcrumbs.enabled": false,
JSON

What’s left is text on a background, in a good font, with syntax colors. And it turns out that when the screen is quiet, the few things that do move are legible again. I notice a squiggle now, because a squiggle is one of maybe three things on screen that isn’t just letters.

Every default in an editor is a decision somebody else made about what deserves your attention.

No tabs

This is the one that gets the loudest reaction, and I get it, because it sounds insane.

"workbench.editor.showTabs": "none". There is no tab bar. There is one file on screen and no visible record of the others.

My case for it is that a tab bar isn’t a navigation tool, it’s an archaeological record. By hour three it’s a row of 11 things where two are what I’m working on, one is a file I opened by accident from a stack trace, one is a .json I peeked at, and the rest are truncated to about nine characters each so I can’t read them anyway. And I still never click them. I’ve been hitting Cmd+P and typing three letters this whole time, because typing three letters is faster than reading 11 truncated words and doing a visual search.

So the tab bar was costing me vertical space and a low-grade sense of clutter in exchange for a feature I’d stopped using without noticing. Once I saw that, the setting was easy.

A few other things fall out of it. Preview mode is off, both from the explorer and from Quick Open, because ghost-italic tabs that replace themselves are a solution to a problem I no longer have. explorer.autoReveal is off so the file tree stops chasing my cursor around. And all five of the gotoLocation.multiple* settings are set to goto, so go-to-definition just goes, instead of opening a little peek window to ask me which of the two candidates I meant.

But subtraction has a bill, and here it comes due: with no tabs and no breadcrumbs, nothing on screen tells you what file you’re in. So the one thing I had to add back is the window title.

settings.json
"window.title": "${dirty}${activeEditorMedium}${separator}${rootName}${separator}${profileName}",
JSON

Dirty indicator, medium-length path, project, profile. It’s the only status readout I kept, and it lives in the one strip of chrome I can’t get rid of anyway, so it’s free.

Hover off, which is the one people actually fight me on

"editor.hover.enabled": "off". No type tooltips, no JSDoc popovers, nothing.

The counter is obvious and it’s good: hover is genuinely important for TypeScript work, and it’s how you read an unfamiliar library without leaving the file. Yeah. I know. I would say two things.

First, a hover tooltip is the only UI I can think of that appears exactly where you were already looking, and covers exactly the code you were reading, on a timer you didn’t set. It’s nondeterministic occlusion triggered by the mouse resting somewhere. If a coworker did that with their hand I’d have a word with them.

Second, I didn’t lose the information, I just made it deliberate. Cmd+K Cmd+I shows the hover on request. Go-to-definition opens the actual source, usually more useful than the summary anyway, and now costs a keystroke instead of an accident.

That said, in the interest of not pretending I’m more resolved about this than I am, here is what’s actually sitting in my settings file:

settings.json
// "[typescript]": {
//   "editor.hover.enabled": "on",
// },
// "[typescriptreact]": {
//   "editor.hover.enabled": "on",
// },
JSON

That block has been commented out for the better part of a year. I keep almost turning it back on and then not doing it. Take the confidence of the preceding four paragraphs accordingly 🙃

The one place I add instead of subtract

"editor.lineHeight": 45 at font size 15. That’s a leading of three, roughly double what any editor ships with and about what you’d use for a poetry collection.

I fully expect this to be the setting people find most objectionable, because it means I see meaningfully fewer lines at once, and “lines visible” is treated as a straightforward good in tooling discourse. But I don’t think density is a virtue in itself, I think it’s a proxy for something, and the something is “can I hold this in my head.” Reading a dense block of code and reading a dense block of prose fail the same way: your eye loses the line, does a return sweep, lands on the wrong one. Typesetters solved this in like the 15th century and the solution was space.

The terminal gets lineHeight: 2 for the same reason, and editor.padding.top is 20 so the first line isn’t jammed against the chrome. The font is MonoLisa with ligatures on, which I paid actual money for and would again.

Enter is a newline

"editor.acceptSuggestionOnEnter": "off". Enter inserts a newline. Always. That’s the whole rule and it doesn’t have exceptions.

The default behavior, where Enter sometimes commits a suggestion you weren’t looking at, means the most common key on the keyboard is contextually overloaded on a popup that appeared unbidden. I’ve written the wrong identifier that way more times than I want to think about, and the failure mode is quiet: it compiles, it’s just not what you meant. Tab commits suggestions instead, which is unambiguous, because Tab in a code editor doesn’t mean much else.

Quick suggestions are off everywhere except inside strings, which sounds backwards until you remember that strings are where the paths and class names live. And the suggest widget renders at font size 12 with a 24 line height, smaller than the editor, so when it does appear it reads as an overlay rather than as more code.

There’s a cluster of smaller ones in the same spirit. editor.emptySelectionClipboard is false, so Cmd+C with nothing selected copies nothing instead of silently copying the whole line and blowing away what was in my clipboard. editor.dragAndDrop is false, because I have never once intentionally moved code with the mouse and have definitely done it by accident. editor.copyWithSyntaxHighlighting is false so pasting into Slack produces text. And editor.multiCursorLimit is 100000, less a considered position than a refusal to ever be told no by my text editor.

Copilot is off, and I’m not making a statement

"github.copilot.enable": { "*": false }, plus inline chat affordance off and the various chat buttons stripped out of the title bar.

Which I want to be careful about, because I’m not staging a protest. The distinction I care about is between an assistant I address and an assistant that finishes my sentences. Ghost text completes into the buffer while I’m mid-thought, which means I’m now proofreading a proposal instead of writing a line, and worse, it’s a slot machine: sometimes it’s exactly right, which is precisely the reinforcement schedule you’d design if you wanted someone to keep pulling the lever.

Whereas a thing I go to deliberately, hand a question, and then read back from is a different relationship entirely. I ask, it answers, I decide. The editor stays a place where the text is mine until I decide otherwise. All else equal, I’d rather have one clear seam between my work and the machine’s than have that boundary blurred continuously at 200ms intervals.

A few unpopular safety opinions

explorer.confirmDelete and confirmDragAndDrop are both off, because I have git and the modal is theater. git.confirmSync likewise. search.searchOnType is off, because I want to type a full query and then search, not watch the editor thrash through five bad prefixes of it.

search.useIgnoreFiles is false, the spicier one. I’d rather my .gitignore not silently decide what I’m allowed to find, since half the reason I’m searching is to look at something generated or vendored. So I hand-exclude node_modules, .next and every image format instead. More setup, but the scope is mine.

The bottom of the file

I’d love to end this on something tidy about intentionality, but the actual bottom of my settings file looks like this:

settings.json
"terminal.integrated.shellIntegration.decorationsEnabled": "never",
"terminal.integrated.fontLigatures.enabled": true,
"workbench.secondarySideBar.defaultVisibility": "hidden",
"chat.titleBar.signIn.enabled": false,
"workbench.editor.editorActionsLocation": "hidden",
"chat.titleBar.openInAgentsWindow.enabled": false,
"workbench.browser.showInTitleBar": false,
"workbench.layoutControl.enabled": false,
JSON

The rest of the file is organized into commented sections. Window, Editor, Typography, Git, Terminal, all in order. And then there’s that, sitting underneath it: a pile of keys the settings UI appended whenever I right-clicked something in the chrome and picked “hide,” none of them sorted into anywhere, about half of them turning off a chat button Microsoft added in an update I didn’t ask for.

It’s sediment. Every couple of months I file it into the sections and then three weeks later there’s more of it, because the actual half-life of my careful organization is however long it takes VS Code to ship a new toolbar.

The habit, not the values

I want to be clear that I’m not recommending any of this. This is a config sanded down by one person over several years to fit one person’s hands, on codebases I mostly already know, and a lot of it would be actively hostile to someone else. If you’re newer, or you spend your days in unfamiliar code, hover and breadcrumbs and the peek window are doing enormous work for you and turning them off would just make you slower and sadder.

The only part I’d actually push on anybody is the habit rather than the values. Every few months, pick one piece of editor chrome you’ve stopped seeing and turn it off for a week. Either you’ll miss it and turn it back on, which costs you eight seconds, or you won’t miss it at all, and that’s worth knowing. I’ve done this maybe 30 times now and I think I’ve turned two things back on.

Good luck out there. May your Enter key mean exactly one thing.

Read similar posts
9 min

Write it down once

Most of what makes Claude Code work for me isn't prompting, it's a few files I wrote once: a plan-mode habit, a /simplify pass welded onto /commit, a hook that won't let me hand-write a commit, and a CLAUDE.md that's mostly scar tissue.

16 min

It said it was working

Porting a Claude Code safety tool from Linux to macOS turned into six weeks of discovering that nearly every failure mode on the new platform was silent, and that the tool's own adversarial test suite was the only reason I could see any of them.