Editor Setup: VS Code

Updated

September 7, 2026

Editor Setup: VS Code with Modern Tooling

After reading this chapter, you will be able to configure Visual Studio Code into a high-performance Python development environment—integrating uv virtual environments, the native Ruff extension for instantaneous format-on-save and auto-import sorting, static type analysis, and reproducible .vscode/ workspace configurations.

Mental model

VS Code acts as an orchestration shell around three specialized background engines: the Language Server (syntax, autocompletion, and type analysis), the Linter & Formatter (Ruff), and the Runtime Debugger (debugpy).

Rather than relying on global editor preferences that vary across machines, professional projects define their toolchain declaratively inside a version-controlled .vscode/ folder within the project repository:

[ Your VS Code Editor ]
          │
          ├──▶ Workspace Configuration: `.vscode/settings.json`
          │    ├── Points Python extension to `${workspaceFolder}/.venv`
          │    └── Sets default formatter to `charliermarsh.ruff`
          │
          ├──▶ Recommended Extensions: `.vscode/extensions.json`
          │    ├── `charliermarsh.ruff` (Instant Rust-based format & lint)
          │    ├── `ms-python.python` (Core Python language support)
          │    └── `tamasfe.even-better-toml` (Syntax for pyproject.toml)
          │
          └──▶ Debug Configurations: `.vscode/launch.json`
               └── Launches Python scripts inside the `uv` virtualenv

When you save a file (Ctrl+S / Cmd+S), the Ruff extension intercepts the write, reorders imports alphabetically, upgrades obsolete syntax, normalizes whitespace, and returns the formatted buffer in less than 5 milliseconds.


Minimal example

To configure any project workspace for modern Python development, create a folder named .vscode in the root of your project directory and add settings.json.

Save this file as .vscode/settings.json:

{
  // 1. Interpreter & Virtual Environment Discovery
  "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
  "python.terminal.activateEnvironment": true,

  // 2. Formatting Engine: Delegate completely to Ruff
  "editor.formatOnSave": true,
  "[python]": {
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "source.fixAll.ruff": "explicit",
      "source.organizeImports.ruff": "explicit"
    }
  },

  // 3. Static Type Analysis via Pylance / Pyright
  "python.analysis.typeCheckingMode": "standard",
  "python.analysis.autoImportCompletions": true,
  "python.analysis.inlayHints.variableTypes": false,
  "python.analysis.inlayHints.functionReturnTypes": true,

  // 4. Test Explorer Integration with Pytest
  "python.testing.pytestEnabled": true,
  "python.testing.unittestEnabled": false,
  "python.testing.pytestArgs": ["tests"],

  // 5. Files & Explorer Hygiene
  "files.exclude": {
    "**/__pycache__": true,
    "**/.pytest_cache": true,
    "**/.ruff_cache": true
  }
}

Now, whenever you open this project in VS Code, all formatting, import sorting, lint fixes, and virtual environment bindings happen automatically on save.


Worked examples

1. Workspace extension recommendations (.vscode/extensions.json)

When collaborating in teams or across machines, you should prompt VS Code to install the exact toolchain required for the project.

Save this file as .vscode/extensions.json:

{
  "recommendations": [
    // Fast Rust-based linter and formatter (replaces Black, Flake8, isort)
    "charliermarsh.ruff",
    // Official Microsoft Python extension
    "ms-python.python",
    // High-performance language server and static type checking
    "ms-python.vscode-pylance",
    // Rich syntax highlighting for pyproject.toml and uv.lock
    "tamasfe.even-better-toml"
  ]
}

Why this matters: When a new contributor opens the repository, VS Code displays a notification: “This repository recommends installing extensions.” Clicking Install All equips their workspace with the exact linter, formatter, and type checker without manual search.


2. Interactive debugging with .vscode/launch.json

VS Code can execute your scripts step-by-step, pause on breakpoints, inspect local variables, and evaluate expressions live in the Debug Console.

Save this file as .vscode/launch.json:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal",
      "justMyCode": true,
      "env": {
        "PYTHONUNBUFFERED": "1"
      }
    },
    {
      "name": "Python: Run CLI with Arguments",
      "type": "debugpy",
      "request": "launch",
      "program": "${workspaceFolder}/src/main.py",
      "args": ["--config", "config.toml", "--verbose"],
      "console": "integratedTerminal",
      "justMyCode": true
    },
    {
      "name": "Python: Debug Pytest",
      "type": "debugpy",
      "request": "launch",
      "module": "pytest",
      "args": ["-v", "${file}"],
      "console": "integratedTerminal",
      "justMyCode": false
    }
  ]
}

To test the debugger: 1. Open any Python file. 2. Click to the left of a line number to set a red breakpoint dot. 3. Press F5 (or select Run → Start Debugging). 4. Execution pauses immediately at your breakpoint, populating the Variables and Call Stack panels.


3. Testing format-on-save and auto-import sorting

To verify that your VS Code workspace is operating correctly, create a test script with intentional formatting defects.

Save this file as editor_verify.py:

# editor_verify.py
import sys
import os
import json
from math import sqrt, pi, sin

def compute_circle_metrics(radius: float) -> dict[str, float]:
    area = pi * (radius ** 2)
    circumference = 2 * pi * radius
    return {"radius": radius, "area": area, "circumference": circumference}

def main() -> None:
    metrics = compute_circle_metrics(5.0)
    print(f"Metrics: {metrics}")

if __name__ == "__main__":
    main()

Notice: - sys, os, json, sqrt, and sin are unused imports. - Now press Ctrl+S (or Cmd+S on macOS).

What happens automatically: 1. Ruff removes the unused imports. 2. Ruff re-formats from math import pi onto a clean single line. 3. Ruff normalizes operators and quote styles according to your pyproject.toml.

The file is saved instantly in its clean canonical form.


Pitfalls

1. Conflicting legacy extensions

The Trap: Leaving legacy extensions like ms-python.black-formatter, ms-python.isort, or ms-python.flake8 enabled alongside charliermarsh.ruff.
The Reality: Two formatters will attempt to format the document simultaneously on save, causing visible cursor jumping, undo-stack corruption, and editor latency.
The Fix: Uninstall or disable Black, isort, and Flake8 extensions. Ruff handles all three functionalities natively.

2. VS Code selecting the wrong Python interpreter

The Trap: VS Code defaulting to global system Python (/usr/bin/python3) instead of the project’s local virtual environment (.venv/bin/python).
The Fix: Press Ctrl+Shift+P (or Cmd+Shift+P), type Python: Select Interpreter, and choose the interpreter located in your project’s ./.venv folder. Specifying "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python" in .vscode/settings.json makes this automatic for anyone opening the folder.

3. Expecting editor tools to run in external shells

The Trap: Assuming that because code formats on save in VS Code, team members using terminal commands or git commits will follow the same rules.
The Fix: Always combine editor configurations with command-line validation. Use uv run ruff check and uv run ruff format --check in your CI pipeline, and enforce .pre-commit-config.yaml git hooks so standards are upheld everywhere.


Exercises

  1. Create a .vscode/settings.json file in a project workspace and verify that enabling "python.analysis.inlayHints.functionReturnTypes": true displays inferred return types for unannotated functions in your editor.
  2. Open the Run and Debug view (Ctrl+Shift+D), create a breakpoint on a function inside editor_verify.py, press F5, and use the Debug Console to evaluate expressions dynamically while paused.
  3. Open the Testing panel (flask icon) in the VS Code sidebar. Write a simple test function test_math(): assert 1 + 1 == 2 in a test_demo.py file, and verify that VS Code discovers and displays a green play button next to the test.
  4. Modify your .vscode/settings.json to configure rulers at 88 and 100 characters by adding "editor.rulers": [88, 100] to visualize line length boundaries.

Further reading

  • VS Code Python Documentation: Getting Started with Python in VS Code (https://code.visualstudio.com/docs/languages/python).
  • Ruff VS Code Extension: Official Ruff extension on VS Code Marketplace (https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
  • Pylance Documentation: Fast, feature-rich Python language support in VS Code (https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance).
  • Astral Documentation: Integrating uv with VS Code (https://docs.astral.sh/uv/guides/integration/editor/).