VS Code
Before you start
You need: any project — the frontend from Track 09 or the Python from Track 13.
Time: about 50 minutes, at the keyboard.
Learning objective
Configure VS Code for the stacks you work in, navigate and edit efficiently, and debug from inside the editor.
Topics
- Workspaces and folders
- Extensions worth installing
- Navigation and editing
- Multi-cursor
- The integrated terminal
- Debugging
- Settings and workspace configuration
- Tasks
- Diagnosing a broken setup
Workspaces
VS Code opens a folder, not a solution. That folder is the workspace, and everything — settings, extensions, the terminal's working directory — is relative to it.
school-portal/
├── .vscode/
│ ├── settings.json workspace settings, committed
│ ├── launch.json debug configurations
│ ├── tasks.json build and run tasks
│ └── extensions.json recommended extensions
├── src/
└── tests/
Commit .vscode/. It means a new developer opens the folder, accepts the recommended extensions, and gets a working debug configuration — rather than spending a morning reproducing yours.
For several related folders, a .code-workspace file groups them:
{
"folders": [
{ "path": "school-portal-api" },
{ "path": "school-portal-web" }
]
}
Extensions
Install what the stack needs and nothing more — every extension costs startup time and memory.
| Stack | Extension |
|---|---|
| .NET | C# Dev Kit (Microsoft) |
| Python | Python + Pylance (Microsoft) |
| Frontend | ESLint, Prettier |
| Angular | Angular Language Service |
| Any | GitLens, EditorConfig, REST Client, Error Lens |
Error Lens shows errors and warnings inline on the line itself rather than only in the Problems panel. It is the single change that most reduces the compile-fix-recompile loop.
REST Client runs HTTP requests from a .http file in the editor — a lightweight alternative to Postman, and the file lives in the repository:
@baseUrl = https://localhost:7099
@token = eyJhbGciOi...
### Get students
GET {{baseUrl}}/api/students?schoolId=1
Authorization: Bearer {{token}}
### Create a student
POST {{baseUrl}}/api/students
Content-Type: application/json
Authorization: Bearer {{token}}
{
"name": "Sneha Patel",
"rollNumber": "NCA-2024-0044",
"className": "9th"
}
// .vscode/extensions.json
{
"recommendations": [
"ms-dotnettools.csdevkit",
"ms-python.python",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"editorconfig.editorconfig"
]
}
VS Code prompts a new developer to install these on first open.
Navigation and editing
| Action | Shortcut |
|---|---|
| Command palette | Ctrl+Shift+P |
| Go to file | Ctrl+P |
| Go to symbol in file | Ctrl+Shift+O |
| Go to symbol in workspace | Ctrl+T |
| Go to definition | F12 |
| Peek definition | Alt+F12 |
| Find all references | Shift+F12 |
| Go to line | Ctrl+G |
| Navigate back | Alt+Left |
| Find in files | Ctrl+Shift+F |
| Rename symbol | F2 |
| Quick fix | Ctrl+. |
| Format document | Shift+Alt+F |
| Toggle terminal | Ctrl+`` |
Ctrl+Shift+P is the one to learn first. Every command is there by name — you never need to find a menu, and typing part of what you want is faster than remembering where it lives.
Ctrl+P accepts modifiers: @ jumps to a symbol, : to a line, > runs a command. Ctrl+P then @get finds every method starting with "get" in the current file.
F2 renames semantically, updating every reference. Find-and-replace does not know the difference between a variable and an unrelated string of the same name.
Multi-cursor
The editing feature that most changes day-to-day speed.
| Action | Shortcut |
|---|---|
| Add cursor above/below | Ctrl+Alt+Up/Down |
| Select next occurrence | Ctrl+D |
| Select all occurrences | Ctrl+Shift+L |
| Add cursor at click | Alt+Click |
| Column selection | Shift+Alt+Drag |
| Expand selection | Shift+Alt+Right |
| Move line up/down | Alt+Up/Down |
| Copy line up/down | Shift+Alt+Up/Down |
| Delete line | Ctrl+Shift+K |
Ctrl+D repeatedly selects the next occurrence of the current word, adding a cursor each time. Editing five of eight occurrences and skipping three is Ctrl+D with Ctrl+K, Ctrl+D to skip — something find-and-replace cannot do.
Shift+Alt+Drag selects a rectangle, which is how you add a prefix to twenty lines at once.
The integrated terminal
Ctrl+` toggles it. It opens in the workspace folder, which is what makes relative paths work.
| Action | Shortcut |
|---|---|
| New terminal | Ctrl+Shift+` |
| Split | Ctrl+Shift+5 |
| Switch | Alt+Left/Right |
| Kill | Ctrl+Shift+P → Kill Terminal |
// .vscode/settings.json
{
"terminal.integrated.defaultProfile.windows": "PowerShell",
"terminal.integrated.cwd": "${workspaceFolder}"
}
Split terminals are how you run a backend and a frontend side by side — dotnet watch run in one, npm run dev in the other.
The terminal inherits the workspace folder, not your shell's last directory. That is usually what you want, and it explains why a relative path works here and not in an external terminal.
Debugging
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET API",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Api/bin/Debug/net9.0/Api.dll",
"cwd": "${workspaceFolder}/src/Api",
"env": { "ASPNETCORE_ENVIRONMENT": "Development" },
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
}
},
{
"name": "Python: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": ["school.api.main:app", "--reload"],
"cwd": "${workspaceFolder}",
"justMyCode": false
},
{
"name": "Attach to Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}/src"
}
]
}
| Action | Shortcut |
|---|---|
| Start | F5 |
| Toggle breakpoint | F9 |
| Step over | F10 |
| Step into | F11 |
| Step out | Shift+F11 |
| Stop | Shift+F5 |
Right-click a breakpoint for a condition, exactly as in Visual Studio:
student.rollNumber === "NCA-2024-0012"
A logpoint prints a message and continues — a console.log that is not in your source and cannot be committed by accident. Right-click the gutter → Add Logpoint.
justMyCode: false lets you step into library code. Leave it true normally; turn it off when you genuinely need to see inside a framework.
serverReadyAction opens the browser when the server prints its listening line — one less manual step per run.
For a frontend, launching Chrome from VS Code means breakpoints in your .ts files work directly, with source maps handled.
Settings
Three levels, each overriding the last: User, Workspace, Folder.
// .vscode/settings.json — committed, applies to everyone
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"editor.rulers": [88, 120],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.exclude": {
"**/bin": true,
"**/obj": true,
"**/__pycache__": true,
"**/node_modules": true
},
"search.exclude": {
"**/dist": true,
"**/coverage": true
},
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.tabSize": 4
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
formatOnSave with a committed formatter configuration ends formatting arguments. Everyone's file is formatted identically, and diffs stop containing whitespace changes.
search.exclude matters more than it looks. Without it, Ctrl+Shift+F searches node_modules and dist, returning thousands of matches in generated code — which makes the feature useless on a real project.
Language-specific blocks let Python use Black and TypeScript use Prettier in the same workspace.
The Python interpreter
Ctrl+Shift+P → Python: Select Interpreter → choose the one in .venv.
This is the single most common VS Code problem in Python work. Code runs in the terminal and VS Code shows import errors, because the editor is using a different interpreter than the activated environment.
{
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe"
}
Committing that path means a new developer gets the right interpreter automatically.
Tasks
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": ["build", "${workspaceFolder}/NexCoding.SchoolPortal.sln"],
"problemMatcher": "$msCompile",
"group": { "kind": "build", "isDefault": true }
},
{
"label": "test",
"command": "dotnet",
"type": "process",
"args": ["test"],
"problemMatcher": "$msCompile",
"group": { "kind": "test", "isDefault": true }
},
{
"label": "watch api",
"command": "dotnet",
"type": "process",
"args": ["watch", "run", "--project", "src/Api"],
"isBackground": true
}
]
}
Ctrl+Shift+B runs the default build task.
problemMatcher is what turns compiler output into clickable entries in the Problems panel. Without it the errors are plain text in the terminal and you navigate to them by hand.
preLaunchTask: "build" in launch.json builds before every debug run, so F5 never runs stale code.
Diagnosing a broken setup
| Symptom | Cause |
|---|---|
| Python imports fail in the editor only | Wrong interpreter selected |
| No IntelliSense for C# | C# Dev Kit not installed, or no project loaded |
F5 does nothing | No launch.json, or the wrong configuration selected |
| Breakpoint is hollow | The source does not match the running build |
| Formatting does nothing | No formatter for that language, or formatOnSave off |
| ESLint not reporting | Extension installed but no config file found |
| Search returns thousands of results | search.exclude not configured |
| Terminal is in the wrong directory | Opened a file, not a folder |
A hollow breakpoint is the informative one. It means the debugger cannot map the source to the loaded module — usually stale build output, or the wrong project launched. Rebuild and check preLaunchTask.
Output panel → the relevant extension's channel shows why an extension is not working. The C# and Python channels report the exact reason far more often than the Problems panel does.
Ctrl+Shift+P → Developer: Reload Window fixes a surprising amount, and it is faster than restarting.
VS Code versus Visual Studio
| VS Code | Visual Studio | |
|---|---|---|
| Weight | Light, fast start | Heavy |
| Platform | Windows, macOS, Linux | Windows (VS for Mac retired) |
| .NET support | Good, via C# Dev Kit | Best in class |
| Frontend support | Excellent | Adequate |
| Python | Excellent | Adequate |
| Designers | None | WinForms, WPF |
| Profiling and diagnostics | Basic | Extensive |
Use Visual Studio for heavy .NET work — a large solution, WinForms or WPF, memory profiling. Use VS Code for frontend, Python, scripts and full-stack work where you want one editor across several stacks.
Most teams use both, and there is no need to choose.
Errors you will hit
| What you see | Cause | Fix |
|---|---|---|
| Breakpoint is hollow | No matching source, or the wrong launch config | Check launch.json |
| Python import fails only in VS Code | Wrong interpreter | Ctrl+Shift+P → Python: Select Interpreter |
| Extension does nothing | Not enabled for this workspace | Check the Extensions panel |
| Formatting differs between machines | No committed settings | Commit .vscode/settings.json |
| Debugger will not attach | Wrong port or process | Check launch.json |
Commit .vscode/settings.json and extensions.json. A new developer then gets the right formatter and the right extensions on first open.
Common mistakes
- Opening a file instead of a folder
- Not committing
.vscode/ - The wrong Python interpreter selected
- Installing dozens of extensions
- Not configuring
search.exclude - No
problemMatcher, so errors are not clickable - No
preLaunchTask, soF5runs stale code console.loginstead of logpoints- Not knowing
Ctrl+Shift+Pexists - Find-and-replace instead of
F2 - Formatter configured per developer rather than in the workspace
Practice
The course exercise is navigate files and use terminals.
- Open a project folder — not a file — and confirm the terminal's working directory.
- Install the extensions for your stack and create
.vscode/extensions.json. - Use
Ctrl+Shift+Pto run five commands you would normally find in a menu. - Find a file with
Ctrl+Pusing a partial name, then a symbol withCtrl+P @. - Navigate a call chain with
F12and return withAlt+Left. - Rename a symbol with
F2. Then rename another with find-and-replace and find what broke. - Use
Ctrl+Dto edit five of eight occurrences, skipping three withCtrl+K, Ctrl+D. - Use
Shift+Alt+Dragto add a prefix to twenty lines at once. - Split the terminal and run a backend and a frontend together.
- Write
launch.jsonfor your stack. Debug with a breakpoint. - Add a conditional breakpoint for one specific record.
- Add a logpoint and confirm it prints without editing the file.
- Create a hollow breakpoint by editing after building. Identify why, then fix it with
preLaunchTask. - Write
tasks.jsonwith aproblemMatcher. Introduce an error and click through from the Problems panel. - Enable
formatOnSavein workspace settings and confirm a colleague's clone formats identically. - Search without
search.excludeand count the results. Add it and repeat. - In a Python project, select the wrong interpreter deliberately and observe the import errors.
- Write a
.httpfile with REST Client and call an API endpoint.
Exercises 13 and 17 are the two setup problems that waste the most time.
You can now
- Configure a workspace so a new developer gets a working setup
- Write a
launch.jsonfor .NET, Python and Chrome - Use multi-cursor editing and the integrated terminal
- Select the right Python interpreter
- Set a logpoint instead of adding a print statement
Review questions
- Why commit the
.vscodefolder? - What does a hollow breakpoint indicate?
- Why does
search.excludematter on a real project? - What is the most common cause of Python import errors appearing only in the editor?
Next: SQL Server and SSMS