Introduction
Most developers start their VSCode setup by installing every extension that looks useful for the language they're learning that week. Six months in, the editor takes four seconds to open, IntelliSense conflicts with itself, three formatters are racing to reformat the same file on save, and the startup notification backlog looks like a dependency graph from hell.
This is not a VSCode problem - it is a configuration discipline problem. VSCode is an exceptionally capable editor with a well-designed extension model, a layered settings system, and first-class support for Language Server Protocol (LSP). But none of that architecture saves you if you pile extensions in without a strategy. The result is an editor that is simultaneously bloated and broken: slow, inconsistent, and requiring constant babysitting.
This guide is for developers who work across multiple languages regularly - or who want to maintain a single editor installation capable of handling all of them well. We will cover PHP, JavaScript, TypeScript, Lua, Java, C, C++, Rust, and Python. The goal is not just to list extensions, but to explain the underlying architecture, identify where conflicts arise, and give you a principled approach to keeping the setup lean and correct over time.
The Polyglot Problem: Why Multi-Language Setups Go Wrong
The core issue with multi-language VSCode setups is that most problems are invisible until they compound. A formatter installed for Python quietly starts affecting JavaScript files. An extension that provides Go-to-Definition for PHP overrides the TypeScript language server's own implementation. A linter for C++ scans Lua files it has no business touching. You notice none of this during setup - only later, when something feels subtly wrong.
There are four primary failure modes to understand.
Extension scope creep is the most common. Extensions frequently activate on file types or languages beyond their stated purpose. This is a consequence of VSCode's activation event model: extensions declare when they activate (onLanguage:python, onStartupFinished, *), and many use the catch-all * or broad startup hooks to ensure they never miss an opportunity to be useful. The result is that installing an extension often means activating it for every project, even when it is irrelevant.
Formatter conflicts occur when multiple extensions register as formatters for the same language. If both Prettier and a language-specific plugin register for TypeScript, VSCode will use whichever responds first or whichever is set as default - and that default setting may be overridden at the workspace level in ways you've forgotten about. The symptom is inconsistent formatting: sometimes you get Prettier's output, sometimes you don't.
LSP collisions happen when two extensions each run a full language server for the same language. This is expensive (two processes, doubled RAM, doubled indexing), but also functionally broken: hover information, diagnostics, and code actions may come from either server depending on timing, leading to contradictory feedback. This is especially common with JavaScript/TypeScript, where the built-in Volar/TypeScript server competes with third-party plugins.
Settings pollution emerges when global settings.json grows unchecked. Developers add language-specific settings globally "just to be safe," and over time the file becomes an archaeological record of every project they've ever worked on. Settings intended for one language silently influence another because the key names are the same.
VSCode's Configuration Architecture: What You're Actually Working With
Before you can solve these problems, you need a clear mental model of VSCode's configuration layers. The editor resolves settings using a cascading priority system, and understanding that cascade is the single most valuable piece of knowledge for any serious VSCode user.
The hierarchy, from lowest to highest priority, is: Default Settings -> User Settings -> Remote Settings -> Workspace Settings -> Workspace Folder Settings. Higher-priority layers override lower ones. This means a workspace-scoped setting always wins over a global user setting for the same key. Most developers know this in principle but don't apply it systematically.
Settings can also be scoped to a language within any of these layers. The syntax is:
{
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.black-formatter"
},
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
This language-scoped block tells VSCode: "when the active file is Python, use Black as the formatter; when it is JavaScript, use Prettier." Without this scoping, a single editor.defaultFormatter applies globally and forces you to pick one formatter for everything - which breaks the moment you open a multi-language monorepo.
Extensions themselves activate based on activation events declared in their package.json. The key events for understanding performance are onLanguage:* (activates when a file of the given language is opened), onStartupFinished (activates after the editor finishes loading), and * (activates always, immediately). Extensions using * are the primary source of startup latency. You can inspect which extensions are active and what they cost using the Extension Host profiler built into VSCode (Help -> Toggle Developer Tools -> Performance tab).
The extensions.json file inside a .vscode/ folder at the workspace root serves a different purpose than many developers realize. It is not a configuration file - it is a recommendation manifest. It tells VSCode which extensions to suggest to anyone who opens this workspace without them installed. It has no effect on which extensions are active for users who already have them installed. Treat it as documentation and onboarding tooling, not as a control mechanism.
The Right Mental Model: Profiles, Workspaces, and Scope Layers
Starting with VSCode 1.75, the editor ships with Profiles - a first-class mechanism for maintaining separate extension sets, settings, keybindings, and UI state per context. This is the architectural answer to the polyglot problem, and it is underused.
A profile is a named, switchable configuration bundle. You can create a "Rust/Systems" profile with rust-analyzer, CodeLLDB, and clangd active, and a separate "Web Development" profile with Prettier, ESLint, and Volar active. Switching profiles is instant. Profiles can be exported as JSON and committed to a dotfiles repository, making your setup reproducible across machines.
The practical strategy is to define profiles along technology boundary lines, not project lines. A good set of base profiles for a polyglot developer might be:
- Core - used for plain text, markdown, shell scripts, config files. Minimal extensions. No language servers.
- Web - JavaScript, TypeScript, CSS, HTML. Prettier, ESLint, Volar/tsserver.
- Backend-JVM - Java, Kotlin. Extension Pack for Java or the individual Language Support for Java extension.
- Systems - C, C++, Rust. clangd, rust-analyzer, CodeLLDB.
- Scripting - Python, Lua, PHP. Pylance, Luacheck, PHP Intelephense.
Within each profile, you still use workspace-scoped settings to tune behavior per project. The profile defines what is available; the workspace defines how it behaves. This two-layer approach keeps global settings minimal and prevents the archaeology problem described earlier.
For teams, workspace settings (.vscode/settings.json) committed to source control are the correct way to enforce consistency. They override each developer's personal settings for anything that matters to the project - formatter, linter rules, file associations, editor ruler positions. Personal preferences (font, theme, keybindings) live in user settings and are never committed.
Language-by-Language Extension Strategy
This section gives the authoritative extension recommendation for each language, with a rationale and what to avoid.
JavaScript and TypeScript
VSCode ships with a built-in TypeScript language server. Do not replace it - extend it. The core extension you need is ESLint (dbaeumer.vscode-eslint) for linting and Prettier (esbenp.prettier-vscode) for formatting. For React/Vue/Svelte work, add the appropriate framework extension (Volar for Vue 3, the official Svelte extension, etc.).
What to avoid: installing multiple IntelliSense extensions for JavaScript. Extensions like VisualStudioExptTeam.vscodeintellicode can enhance the built-in server but should not replace it. Avoid any extension that claims to "improve" TypeScript IntelliSense by installing a separate language server - this is almost always a downgrade.
// .vscode/settings.json - JavaScript/TypeScript workspace
{
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"typescript.tsdk": "node_modules/typescript/lib"
}
The typescript.tsdk line is important in any workspace that ships its own TypeScript version - it ensures VSCode uses the project's TypeScript rather than its own bundled copy, eliminating version drift between your editor feedback and your build output.
Python
The ecosystem here has stabilized around Pylance (ms-python.vscode-pylance) as the language server and Black (ms-python.black-formatter) or Ruff (charliermarsh.ruff) as the formatter. Ruff is increasingly preferred because it consolidates linting and formatting into a single fast Rust-based tool, eliminating the need for separate Flake8, isort, and Black installations.
{
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
}
},
"python.languageServer": "Pylance"
}
A common mistake is activating both mypy (via the official mypy extension) and Pylance's type checking simultaneously. Both will emit type diagnostics, leading to duplicate or contradictory errors. If you use mypy for CI, set "python.analysis.typeCheckingMode": "off" in your workspace and let mypy handle typing; use Pylance only for IntelliSense and navigation.
Rust
rust-analyzer (rust-lang.rust-analyzer) is the only extension you need. It is a full language server that handles IntelliSense, diagnostics, formatting (via rustfmt), code actions, and macro expansion. There is no meaningful alternative. For debugging, CodeLLDB (vadimchet.codelldb) is the standard choice on Linux and macOS; the MSVC debugger extension is preferred on Windows.
Avoid the legacy Rust extension (rust-lang.rust), which uses the now-deprecated RLS (Rust Language Server). If it is installed alongside rust-analyzer, disable it - they will conflict on diagnostics.
{
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"rust-analyzer.checkOnSave": true,
"rust-analyzer.cargo.features": "all"
}
C and C++
The dominant extension is clangd (llvm-vs-code-extensions.vscode-clangd) over the older C/C++ extension from Microsoft. clangd is a production-grade LSP server that ships as part of the LLVM project. It is faster, more accurate, and integrates directly with your compile_commands.json, which most modern build systems (CMake, Bazel, Meson) can generate automatically.
The critical step is generating compile_commands.json for your project. With CMake:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build .
ln -s build/compile_commands.json compile_commands.json
Without this file, clangd operates in a degraded mode with incomplete includes and wrong diagnostics. This is the number one reason developers abandon clangd and fall back to the Microsoft extension - not a limitation of clangd, but a missing setup step.
{
"[c]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"[cpp]": {
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
},
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--header-insertion=iwyu"
]
}
Do not install both clangd and the Microsoft C/C++ extension with IntelliSense enabled. They run two separate language servers and will conflict. If you need the Microsoft extension for debugging (it provides cppdbg), disable its IntelliSense features explicitly:
{
"C_Cpp.intelliSenseEngine": "disabled"
}
Java
The Extension Pack for Java from Microsoft (vscjava.vscode-java-pack) is the accepted baseline. It bundles Language Support for Java (Eclipse JDT-based LSP), Debugger for Java, Test Runner for Java, Maven/Gradle support, and a project manager. Installing the pack gives you a coherent, integrated toolchain without needing to manually assemble compatible versions.
Java's LSP has a notably long startup time because it forks a full JVM. Set "java.jdt.ls.vmargs" to allocate more heap if you work on large projects:
{
"java.jdt.ls.vmargs": "-Xmx2G -XX:+UseG1GC",
"java.configuration.runtimes": [
{ "name": "JavaSE-21", "path": "/usr/lib/jvm/java-21-openjdk" }
]
}
The java.configuration.runtimes entry is essential in environments with multiple JDKs. Without it, the LSP may silently use the wrong version and produce misleading diagnostics about sealed classes or pattern matching syntax.
PHP
PHP Intelephense (bmewburn.vscode-intelephense-client) is the most capable PHP extension for VSCode, offering full stub-based IntelliSense for PHP's core APIs, fast indexing, and accurate go-to-definition. The free tier covers most use cases; the premium tier adds code folding, rename refactoring, and implement interface/override method actions.
Disable the built-in PHP language feature extension that ships with VSCode when using Intelephense, to avoid duplicate hover and completion providers:
{
"php.suggest.basic": false,
"php.validate.enable": false
}
For formatting, PHP CS Fixer is the standard. It does not have an official VSCode extension, but the PHP CS Fixer extension by junstyle wraps it adequately. Configure it to use your project's .php-cs-fixer.php configuration file so that editor formatting matches your CI pipeline.
Lua
The Lua extension by sumneko (sumneko.lua) is the standard choice - it is a full LSP server specifically for Lua, with support for configuring different runtime environments (LuaJIT, Lua 5.1/5.2/5.3/5.4, LÖVE, Neovim). The environment configuration is important because Lua's standard library differs across versions, and incorrect configuration produces spurious "undefined global" warnings.
{
"[lua]": {
"editor.defaultFormatter": "sumneko.lua"
},
"Lua.runtime.version": "LuaJIT",
"Lua.diagnostics.globals": ["vim"],
"Lua.workspace.library": []
}
The Lua.diagnostics.globals array is critical for Neovim config files or game engines that inject global variables. Without it, the LSP will warn about every use of vim, love, or hs as an undefined global, generating noise that trains you to ignore diagnostics - which defeats the purpose of having a linter.
Conflict Detection and Resolution
Before optimizing, you need to know what is actually running. VSCode provides two essential diagnostic tools that most developers overlook.
The Process Explorer (Help -> Open Process Explorer) shows all running processes spawned by VSCode, including extension host workers and language server processes. If you see node processes you cannot identify, or multiple instances of the same language server binary, you have a conflict. Language servers are typically listed by their binary name: clangd, rust-analyzer, pylsp, intelephense, etc.
The Extension Host Performance profiler (Help -> Toggle Developer Tools, then run workbench.action.startExtensionHostProfile from the command palette) records a CPU profile of extension activity during startup. This directly shows you which extensions are consuming time. Run it once on a clean startup and look for anything taking more than 200ms - those are your primary optimization targets.
A practical conflict resolution workflow:
- Open the Extensions sidebar and filter by "enabled" for your current workspace.
- For any language you are not actively working on in this workspace, disable the extension at the workspace level rather than globally. Right-click -> "Disable (Workspace)."
- For formatters, explicitly verify the
editor.defaultFormattersetting for each language you use. Open a file of that type and runFormat Document With...from the command palette - it shows all registered formatters for that language. If more than one appears, you have a conflict and should disable the unwanted one. - For LSP conflicts, check the Output panel (
View -> Output) and select each language server from the dropdown. If you see two servers emitting diagnostics for the same file, disable one.
The output panel is also the right place to diagnose language server startup failures. Errors like "Failed to start language server" or "client: couldn't create connection to server" appear there - not in the editor's diagnostic pane, which only shows errors in your code, not in the tooling.
Formatter Wars: How to Stop Tools From Fighting Each Other
Formatter conflicts are the most common source of day-to-day frustration in multi-language setups. The canonical solution is two rules applied consistently.
Rule 1: One formatter per language, defined explicitly. Always set editor.defaultFormatter inside a [language] block, never globally. A global editor.defaultFormatter is a bug waiting to manifest - it applies to every language without a more specific override, which means the first time you open a file type you haven't configured, you will get an unexpected formatter. Prefer making the absence of a formatter explicit:
{
"editor.defaultFormatter": null,
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"
},
"[lua]": {
"editor.defaultFormatter": "sumneko.lua"
},
"[java]": {
"editor.defaultFormatter": "redhat.java"
}
}
Setting editor.defaultFormatter to null globally means: if a language does not have an explicit formatter configured, format nothing. This surfaces missing configuration rather than silently applying the wrong formatter.
Rule 2: Format-on-save must match CI. The only formatting that matters for a team is what your CI pipeline enforces. If format-on-save uses a different configuration than your CI linter, you will have an endless loop of "fix CI" commits. The solution is to always point your editor formatter to the same configuration file that CI uses. For Prettier, this means a .prettierrc in the repo root. For Black/Ruff, a pyproject.toml. For PHP CS Fixer, a .php-cs-fixer.php. Never configure formatting rules in VSCode settings directly - configure them in the project's config file and point the extension to it.
A particularly subtle conflict arises with ESLint and Prettier in JavaScript/TypeScript projects. ESLint has style rules (spacing, semicolons, quotes) that overlap with what Prettier enforces. If both are active without coordination, they will disagree and produce feedback on every file. The standard resolution is eslint-config-prettier, which disables all ESLint rules that Prettier handles. Install it as a dev dependency and extend from it in your ESLint config:
// .eslintrc.json
{
"extends": ["eslint:recommended", "prettier"]
}
This makes ESLint responsible for code correctness (unused variables, no-console, type errors via typescript-eslint) and Prettier responsible for aesthetics. They no longer overlap.
LSP Coordination: One Language Server Per Language
Language Server Protocol is the backbone of VSCode's intelligence features. Every hover tooltip, every go-to-definition, every inline diagnostic is mediated by an LSP server. The protocol is well-specified - it is the multiplicity of servers that causes problems.
The principle is simple: one language server per language per workspace. The implementation requires knowing which extensions bundle language servers and which ones add features on top of an existing server.
Extensions that bundle a full LSP server (and therefore conflict with others doing the same):
rust-lang.rust-analyzer(Rust)ms-python.pylance(Python)llvm-vs-code-extensions.vscode-clangd(C/C++)ms-vscode.cpptoolswhen IntelliSense is enabled (C/C++)bmewburn.vscode-intelephense-client(PHP)sumneko.lua(Lua)redhat.java/vscjava.vscode-java-pack(Java)
Extensions that add features on top of an existing LSP server (generally safe to layer):
dbaeumer.vscode-eslint- adds ESLint diagnostics alongside TypeScript's owncharliermarsh.ruff- adds Ruff diagnostics/formatting alongside Pylancems-python.black-formatter- adds Black formatting alongside Pylance
The practical test: if an extension's README says "provides IntelliSense" or "language server," assume it is a full LSP bundle. If it says "linter," "formatter," or "code actions," it is likely an overlay.
For TypeScript specifically, understand the difference between the built-in TypeScript language features extension (part of VSCode core, cannot be disabled) and third-party TypeScript extensions. Extensions like vscode-eslint operate at the diagnostic layer - they add errors and warnings - but do not replace the underlying language server. This is the safe pattern.
Workspace-Scoped Settings: The Right Way to Specialize
The .vscode/settings.json file committed to a repository is one of the highest-leverage practices available to a team. Used correctly, it eliminates an entire class of "works on my machine" problems related to editor behavior.
The principle: workspace settings handle anything that affects how the code behaves in the editor and that must be consistent across developers. User settings handle personal preference.
A well-structured .vscode/settings.json for a TypeScript monorepo:
{
// Formatting - consistent with CI
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// TypeScript - use project's local version
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
// ESLint - validate all relevant file types
"eslint.validate": ["typescript", "typescriptreact"],
"eslint.workingDirectories": [{ "mode": "auto" }],
// Files - consistent line endings and encoding
"files.eol": "\n",
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
// Exclude build artifacts from file watcher and search
"files.watcherExclude": {
"**/node_modules/**": true,
"**/dist/**": true,
"**/.git/objects/**": true
},
"search.exclude": {
"**/node_modules": true,
"**/dist": true
}
}
The files.watcherExclude and search.exclude settings deserve special attention. VSCode's file watcher monitors the file system for changes to trigger reindexing and auto-refresh. In large projects or monorepos, watching node_modules or build output directories consumes significant CPU and RAM. Excluding them is not optional in serious development - it is a baseline.
For C/C++ projects, a workspace settings file should also specify the path to compile_commands.json, the target architecture if cross-compiling, and the include paths for any system headers not discoverable automatically. For Java, it should pin the JDK version. For Python, it should specify the interpreter path or the virtual environment directory. These are the settings that turn a generic editor into a project-aware development environment.
The Debloated Extension Audit
Performing a periodic audit of your installed extensions is as important as any other maintenance task. The audit has three phases.
Phase 1: Inventory. Export your current extension list from the terminal:
code --list-extensions > extensions-audit.txt
Open the list and, for each extension, answer three questions: (1) What language or task is this for? (2) When did I last open a file that needed it? (3) Is there a conflict risk with another extension in this list?
Phase 2: Classify and prune. Classify each extension into one of three categories:
- Core: Used in at least one active project, provides a language server or a fundamental feature (formatting, debugging). Keep globally or in a profile.
- Project-specific: Needed only for a specific repository (e.g., a Dockerfile linter, a specific framework's snippet pack). Move to workspace recommendations only - do not keep globally.
- Zombie: Installed for a project that ended, for a language you no longer use, or "just in case." Uninstall.
The zombie category is typically 30-50% of a developer's installed extensions if they have never audited. These extensions activate on startup, consume memory, and contribute to the kind of subtle interference described throughout this guide.
Phase 3: Establish a policy. Decide on a rule for installing new extensions: always install to a profile, never globally unless it is language-agnostic tooling (like GitLens, which works across all projects). When a project ends, do a thirty-second review of workspace-specific extensions and uninstall or demote them.
The target extension count for a global installation is low: a handful of cross-cutting tools (Git integration, theme, snippets, Remote SSH if needed) and nothing language-specific. Language extensions belong in profiles. This is the structural answer to bloat - not discipline about not installing things, but a profile system that makes it natural to isolate concerns.
Trade-offs and Pitfalls
No setup is perfect, and a principled multi-language configuration introduces its own friction points worth understanding.
Profile switching friction. Switching profiles requires a window reload. If you frequently jump between, say, Python and Rust work, this becomes annoying. The practical mitigation is to open each language context in a separate window (VSCode supports multiple windows, each with different profile assignments) rather than switching profiles in the same window. You can open a new window with a specific profile from the command palette: New Window with Profile.
Workspace settings vs. personal settings tension. Committed workspace settings are great for teams but can override personal preferences in ways that frustrate individuals. The right scope boundary: workspace settings control things that affect code quality and consistency (formatters, linters, file encoding, TypeScript version). They should not control editor aesthetics (font, theme, minimap visibility) or personal workflow (terminal profile, keybindings). If a workspace setting is controlling something personal, it is in the wrong scope.
Language servers and large monorepos. In a monorepo containing multiple languages, running all language servers simultaneously is expensive. A 16GB RAM machine can comfortably run two or three language servers concurrently - Java's JDT, rust-analyzer, and Pylance together might consume 2-4GB. If you are running more than three simultaneously, you will feel it. The mitigation is to use VSCode's workspace folders feature to open only the subtree you are working on, which limits which language servers activate.
Extension auto-updates. VSCode updates extensions automatically by default. This is convenient but can introduce unexpected behavior when a formatter or LSP update changes output. For production-critical setups or team environments, consider disabling auto-updates ("extensions.autoUpdate": false) and updating extensions deliberately on a schedule where you can observe the changes.
Best Practices
The following practices synthesize everything in this guide into an actionable baseline.
Use Profiles as your primary isolation mechanism. Create one profile per language family. Keep your global installation nearly empty. This is the single change with the highest impact on both performance and maintainability.
Commit .vscode/settings.json to every repository. This file should define the formatter, any language-specific LSP settings, file watcher exclusions, and files.eol/files.encoding. It should not define personal preferences. Make it part of your project template.
Audit compile_commands.json for every C/C++ project. The most common source of clangd problems is this file being missing or stale. Integrate its generation into your build script, not as a manual step.
Disable, don't uninstall, when testing. When diagnosing a conflict, disable extensions rather than uninstalling them. This lets you re-enable quickly if the extension was not the problem. Only uninstall once you are confident.
Use the Output panel as your primary diagnostic tool. Before filing a GitHub issue or asking for help, check View -> Output and read what the language server is telling you. Most LSP problems are diagnosed in thirty seconds this way.
Match editor formatters to CI formatters, always. The formatter in your editor is only meaningful if it matches what runs in CI. Enforce this by pointing extensions to the project's config file, never by embedding formatter rules in .vscode/settings.json.
Set explicit editor.defaultFormatter per language. Never rely on the global default. Make every language's formatter an explicit, intentional choice in your settings.
Run the Extension Host profiler at least once. Most developers have never done this. Run it, look at the top ten extensions by activation time, and ask whether you actually use them. You will almost always find at least one expensive extension you had forgotten about.
Key Takeaways
The 80/20 insight: The vast majority of VSCode performance and correctness problems in multi-language setups come from three things: globally-installed language-specific extensions that should be in profiles, formatters configured at the global level rather than per-language, and missing compile_commands.json for C/C++ projects. Fix these three and you eliminate most problems.
Five steps you can apply today:
- Run
code --list-extensionsand identify extensions you haven't used in over two months. Uninstall them. - Open your User
settings.jsonand audit every formatter-related key. Move them into[language]scoped blocks. - Create one Profile for each language family you work in. Move language-specific extensions into the appropriate profile.
- Open a C/C++ project and verify that
compile_commands.jsonexists at the root. If it does not, generate it via your build system. - For any project with a team, create
.vscode/settings.jsonwith at minimumeditor.defaultFormatterper language,files.eol, andsearch.exclude.
Analogy: Configuring VSCode for multiple languages is like provisioning a workshop. A professional workshop does not keep every tool out on every workbench at all times - it organizes tools by task, puts specialist tools in drawers, and keeps only the daily-use tools within reach. Profiles are your drawers. The global installation is your workbench. Keep the workbench clear.
References
- Visual Studio Code Documentation - User and Workspace Settings: https://code.visualstudio.com/docs/getstarted/settings
- Visual Studio Code Documentation - Profiles in Visual Studio Code: https://code.visualstudio.com/docs/editor/profiles
- Visual Studio Code Documentation - Extensions API - Activation Events: https://code.visualstudio.com/api/references/activation-events
- Microsoft Language Server Protocol Specification: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
- rust-analyzer User Manual: https://rust-analyzer.github.io/manual.html
- clangd Documentation - Project Setup: https://clangd.llvm.org/installation.html
- Pylance FAQ and Configuration (Microsoft Python Extension): https://github.com/microsoft/pylance-release
- PHP Intelephense Documentation: https://intelephense.com
- sumneko Lua Language Server Wiki: https://github.com/LuaLS/lua-language-server/wiki
- Prettier Documentation - Integrating with Linters: https://prettier.io/docs/en/integrating-with-linters.html
eslint-config-prettier- eslint-config-prettier on npm: https://github.com/prettier/eslint-config-prettier- Ruff Documentation: https://docs.astral.sh/ruff/
- VSCode Extension Pack for Java: https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack
- CMake -
CMAKE_EXPORT_COMPILE_COMMANDSdocumentation: https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html