Repository: owid/etl
Pull request #4058: 🎉 Create VSCode extension to execute code inside run
Author: login: pabloarosado
Created: 2025-03-04T09:00:17Z | Merged: 2025-03-07T11:01:49Z

## Context
When working on ETL data steps, most of use use VSCode with an Interactive Window. Usually, what I do is `shift+a` to select the entire script, then `shift+Enter` to execute it. An interactive window opens. Then I scroll down to the beginning of the `run()` function, select a block of code, and hit `shift+Enter`.

If I want to re-execute the entire content of `run()` until a certain moment, I need to manually press `shift` and up or down to select the appropriate block of code, and then `shift+Enter` to execute it. This is a bit time consuming.

Also, when working on a snapshot step, if you do `cmd+a` and `shift+Enter`, the interactive window opens, but the code fails (because of the `if __name__ == "__main__"`. So you have to manually select only the code above this block.

So, the extension in this PR makes this workflow faster:
* If you are outside of `run()` (e.g. when you just opened the script) hit `cmd+Enter` to initialize an interactive window, and execute the entire script (to have all definitions in memory). The cursor will be moved to the first line inside `run()`.
* If you are inside of `run()`, hit `cmd+Enter` to execute all code inside `run()` until the current line.
* If you are working on a snapshot, the `if __name__ == "__main__"` will be ignored.

NOTE: You can execute an entire step by simply hitting `cmd+Enter`. First time you press it, you initialize the interactive window and execute all definitions. Then, the cursor moves to the inside of `run()`, and if you hit `cmd+Enter` multiple times, it will run the block line by line (every time running the entire block). This, plus some caching, let's you "debug" through the code.

## Main changes
* Let `make test` install custom VSCode extensions (if `code` is installed, otherwise skip). This will also install the previously created `find-latest-etl-step` extension.
* Create a new VSCode extension that helps when writing ETL data pipelines.
* I added a couple of parameters to `.vscode/settings`. They are some time delays that are used to ensure the extension works well. We should try it on different computers to see if it works. If not, a user could try setting different delays (possibly longer for a slower computer). I'm not sure if this is totally necessary, we may remove it otherwise.

## Discussion
I have been working with this extension in the last couple of days and found it quite useful! But I'm wondering a few things:
1. Is it working by default? E.g. if anyone does `make install` and reloads VSCode, does the `cmd+Enter` shortcut work?
2. Is the default behavior when the cursor is outside `run()` the most convenient one? If you are inside another function, you may think that `cmd+Enter` should run the content of that function until the cursor position. However, what it will do is execute the entire script and move the cursor to the first line of `run()`. This could be confusing.
  * I think the current behavior is the most convenient one for ETL work, but we can redefine it if we find a better option.
4. Is the default behavior when the cursor is inside `run()` the most convenient one? Currently, it selects all code above the cursor (including the current line) and executes it in the interactive window. Maybe it would be more convenient to do that, and remove the selection.
  * I think the current behavior is reasonable.

## Other ideas
Another similar extension (maybe assigned to `ctrl+Enter`, or replace the default `shift+Enter`) could be one that:
  * If there is a selection, execute that.
  * If nothing is selected, selects the current block (based on where the cursor is), and execute it in the interactive window. This would be more similar to how R-studio works (which, in this sense, is a bit smarter than the VSCode interactive window, since here you need to select the full block manually).
Optionally, it could move to the next line of code. This way, you could run an entire script by simply hitting `ctrl+Enter` multiple times (similar to debugging line by line).

I tried creating this extension (and called it `smart-run-current-line`) but I couldn't manage to make it work. There were always some annoying edge cases, so I gave up.



Diff:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 3f21ee9981b..3fb35d18cd5 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,7 +10,7 @@
       "**/*.meta.yml",
       "**/*.meta.override.yml"
     ],
-    "schemas/multidim-schema.json": "export/multidim/**/*.yml",
+    "schemas/multidim-schema.json": "export/multidim/**/*.yml"
   },
   "[python]": {
     "editor.formatOnSave": true,
@@ -50,5 +50,9 @@
     {
       "file": ".copilot-commit-message-instructions.md"
     }
-  ]
+  ],
+
+  // Settings for the "Run Until Cursor" extension
+  "runUntilCursor.execDelay": 100,    // Delay before executing code (default: 100ms)
+  "runUntilCursor.cursorDelay": 500   // Delay before moving the cursor into run() (default: 500ms)
 }
diff --git a/Makefile b/Makefile
index e83e9940fb8..c25cf439cc4 100644
--- a/Makefile
+++ b/Makefile
@@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
 
 test-integration: .venv
 	@echo '==> Running integration tests'
@@ -145,3 +145,32 @@ fasttrack: .venv
 wizard: .venv
 	@echo '==> Starting Wizard on http://localhost:8053/'
 	.venv/bin/etlwiz
+
+# If VSCode exists, install a list of published extensions (defined in EXTENSIONS) and a list of custom extensions (defined in CUSTOM_EXTENSIONS).
+# Custom extensions are expected to be in the vscode_extensions folder, with a subfolder for each extension containing a folder install/ with a VSIX file.
+# The latest VSIX file in each install/ folder will be installed.
+install-vscode-extensions:
+	@echo '==> Checking and installing required VS Code extensions'
+	@if command -v code > /dev/null; then \
+		EXTENSIONS="ms-toolsai.jupyter"; \
+		CUSTOM_EXTENSIONS="run-until-cursor find-latest-etl-step"; \
+		EXTENSIONS_PATH="vscode_extensions"; \
+		for EXT in $$EXTENSIONS; do \
+			if ! code --list-extensions | grep -q "$$EXT"; then \
+				code --install-extension $$EXT; \
+			fi; \
+		done; \
+		for EXT in $$CUSTOM_EXTENSIONS; do \
+			if ! code --list-extensions | grep -q "owid.$$EXT"; then \
+				VSIX_FILE=$$(ls -v $$EXTENSIONS_PATH/$$EXT/install/$$EXT-*.vsix 2>/dev/null | tail -n 1); \
+				if [ -n "$$VSIX_FILE" ]; then \
+					echo "Installing owid.$$EXT from $$VSIX_FILE"; \
+					code --install-extension "$$VSIX_FILE"; \
+				else \
+					echo "⚠️ No VSIX file found for owid.$$EXT. Skipping."; \
+				fi; \
+			fi; \
+		done; \
+	else \
+		echo "⚠️ VS Code CLI (code) is not installed. Skipping extension installation."; \
+	fi
diff --git a/vscode_extensions/README.md b/vscode_extensions/README.md
index 8e5fc29e796..981a61a54e6 100644
--- a/vscode_extensions/README.md
+++ b/vscode_extensions/README.md
@@ -48,3 +48,8 @@ This creates a new .vsix file for the new version.
 ## Install an extension
 
 Press `cmd+shift+p` and select "Extensions: Install from VSIX". Select the `*.vsix` file of the extension you want to install. Restart VSCode to ensure the new version is installed (although it may not be necessary).
+
+Alternatively, from the command line, go to the root folder of the extension code and run:
+```
+code --install-extension [name-of-the-extension-with-version].vsix
+```
diff --git a/vscode_extensions/find-latest-etl-step/install/find-latest-etl-step-0.0.2.vsix b/vscode_extensions/find-latest-etl-step/install/find-latest-etl-step-0.0.2.vsix
index 1bf1f8b3198..3834872e918 100644
Binary files a/vscode_extensions/find-latest-etl-step/install/find-latest-etl-step-0.0.2.vsix and b/vscode_extensions/find-latest-etl-step/install/find-latest-etl-step-0.0.2.vsix differ
diff --git a/vscode_extensions/find-latest-etl-step/package.json b/vscode_extensions/find-latest-etl-step/package.json
index c8c8996722f..f0d559bf5a3 100644
--- a/vscode_extensions/find-latest-etl-step/package.json
+++ b/vscode_extensions/find-latest-etl-step/package.json
@@ -3,6 +3,7 @@
   "displayName": "Find Latest ETL Step",
   "description": "Find the latest ETL step given a step short name",
   "version": "0.0.2",
+  "publisher": "owid",
   "engines": {
     "vscode": "^1.93.0"
   },
diff --git a/vscode_extensions/run-until-cursor/README.md b/vscode_extensions/run-until-cursor/README.md
new file mode 100644
index 00000000000..50ce119ac3e
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/README.md
@@ -0,0 +1,34 @@
+# Run Until Cursor
+
+This extension allows you to **execute all lines inside `run()` up to the line where the cursor currently is** in the VS Code **Interactive Window**.
+
+## Usage
+* If the cursor is anywhere outside the `run()` function, press `cmd+Enter` to execute the entire script in the interactive window. After doing this, the cursor will be moved to the beginning of the `run()` function.
+  * This is useful when you just opened a new data step script and haven't yet initialized an interactive window.
+* Once an interactive window has already been initialized, if the cursor is inside the `run()` function, press `cmd+Enter` to execute all the code inside the function until the current line (the current line will also be executed).
+  * This is useful while working on a data step, when you want to quickly test the entire content of `run()` from the beginning.
+
+## Installation
+
+You don't have to install it manually if you are on a working ETL environment. Just running `make test` should install the latest version of this extension.
+
+However, if you want to manually install this extension, you can:
+- Press `cmd+shift+p` and select "Extensions: Install from VSIX".
+- Select the latest version of the packaged extension, e.g. `install/run-until-cursor-0.0.1.vsix`.
+- Restart VSCode to ensure the new version is installed (although it may not be necessary). You can do it by doing `cmd+shift+p` and selecting "Developer: Reload Window".
+
+Alternatively, from the command line, go to `vscode_extensions/run-until-cursor/` and run:
+```
+code --install-extension install/run-until-cursor-0.0.1.vsix
+```
+(or any other version).
+
+## Development
+
+To make changes to the src/extension.ts, first install dependencies (`npm install`) from the folder `run-until-cursor`. Then, after any changes in the code:
+```
+npm run compile
+vsce package
+code --install-extension run-until-cursor-0.0.1.vsix
+```
+(or whatever version is specified in `package.json`). Then, from VSCode, `cmd+shift+p` and select "Developer: Reload Window".
diff --git a/vscode_extensions/run-until-cursor/esbuild.config.js b/vscode_extensions/run-until-cursor/esbuild.config.js
new file mode 100644
index 00000000000..013c677c860
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/esbuild.config.js
@@ -0,0 +1,9 @@
+require("esbuild").build({
+    entryPoints: ["src/extension.ts"],
+    bundle: true,
+    outfile: "dist/extension.js",
+    external: ["vscode"],  // Ensure vscode is NOT bundled
+    format: "cjs",         // Change to CommonJS format
+    platform: "node"
+}).catch(() => process.exit(1));
+
diff --git a/vscode_extensions/run-until-cursor/install/run-until-cursor-0.0.1.vsix b/vscode_extensions/run-until-cursor/install/run-until-cursor-0.0.1.vsix
new file mode 100644
index 00000000000..2d934ccf324
Binary files /dev/null and b/vscode_extensions/run-until-cursor/install/run-until-cursor-0.0.1.vsix differ
diff --git a/vscode_extensions/run-until-cursor/package-lock.json b/vscode_extensions/run-until-cursor/package-lock.json
new file mode 100644
index 00000000000..73f701f04bd
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/package-lock.json
@@ -0,0 +1,431 @@
+{
+  "name": "run-until-cursor",
+  "version": "0.0.1",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "run-until-cursor",
+      "version": "0.0.1",
+      "devDependencies": {
+        "@types/vscode": "^1.97.0",
+        "esbuild": "^0.19.2"
+      },
+      "engines": {
+        "vscode": "^1.97.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+      "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+      "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+      "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+      "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+      "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+      "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+      "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+      "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+      "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+      "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+      "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+      "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+      "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+      "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+      "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+      "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+      "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+      "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+      "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+      "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+      "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+      "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+      "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@types/vscode": {
+      "version": "1.97.0",
+      "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.97.0.tgz",
+      "integrity": "sha512-ueE73loeOTe7olaVyqP9mrRI54kVPJifUPjblZo9fYcv1CuVLPOEKEkqW0GkqPC454+nCEoigLWnC2Pp7prZ9w==",
+      "dev": true
+    },
+    "node_modules/esbuild": {
+      "version": "0.19.12",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+      "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.19.12",
+        "@esbuild/android-arm": "0.19.12",
+        "@esbuild/android-arm64": "0.19.12",
+        "@esbuild/android-x64": "0.19.12",
+        "@esbuild/darwin-arm64": "0.19.12",
+        "@esbuild/darwin-x64": "0.19.12",
+        "@esbuild/freebsd-arm64": "0.19.12",
+        "@esbuild/freebsd-x64": "0.19.12",
+        "@esbuild/linux-arm": "0.19.12",
+        "@esbuild/linux-arm64": "0.19.12",
+        "@esbuild/linux-ia32": "0.19.12",
+        "@esbuild/linux-loong64": "0.19.12",
+        "@esbuild/linux-mips64el": "0.19.12",
+        "@esbuild/linux-ppc64": "0.19.12",
+        "@esbuild/linux-riscv64": "0.19.12",
+        "@esbuild/linux-s390x": "0.19.12",
+        "@esbuild/linux-x64": "0.19.12",
+        "@esbuild/netbsd-x64": "0.19.12",
+        "@esbuild/openbsd-x64": "0.19.12",
+        "@esbuild/sunos-x64": "0.19.12",
+        "@esbuild/win32-arm64": "0.19.12",
+        "@esbuild/win32-ia32": "0.19.12",
+        "@esbuild/win32-x64": "0.19.12"
+      }
+    }
+  }
+}
diff --git a/vscode_extensions/run-until-cursor/package.json b/vscode_extensions/run-until-cursor/package.json
new file mode 100644
index 00000000000..f0e82c1ae4a
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/package.json
@@ -0,0 +1,37 @@
+{
+  "name": "run-until-cursor",
+  "displayName": "Run Until Cursor",
+  "description": "Executes lines inside `run()` up to the cursor in the Interactive Window.",
+  "version": "0.0.1",
+  "publisher": "owid",
+  "engines": {
+    "vscode": "^1.97.0"
+  },
+  "categories": ["Other"],
+  "activationEvents": ["onCommand:extension.runUntilCursor"],
+  "contributes": {
+    "commands": [
+      {
+        "command": "extension.runUntilCursor",
+        "title": "Run Until Cursor",
+        "category": "Python"
+      }
+    ],
+    "keybindings": [
+      {
+        "command": "extension.runUntilCursor",
+        "key": "cmd+enter",
+        "when": "editorTextFocus && editorLangId == python"
+      }
+    ]
+  },
+  "main": "./dist/extension.js",
+  "scripts": {
+    "compile": "node esbuild.config.js",
+    "watch": "node esbuild.config.js --watch"
+  },
+  "devDependencies": {
+    "@types/vscode": "^1.97.0",
+    "esbuild": "^0.19.2"
+  }
+}
diff --git a/vscode_extensions/run-until-cursor/src/extension.ts b/vscode_extensions/run-until-cursor/src/extension.ts
new file mode 100644
index 00000000000..8da8d58d536
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/src/extension.ts
@@ -0,0 +1,128 @@
+import * as vscode from "vscode";
+
+export function activate(context: vscode.ExtensionContext) {
+    let disposable = vscode.commands.registerCommand("extension.runUntilCursor", async () => {
+        const editor = vscode.window.activeTextEditor;
+        if (!editor) {
+            vscode.window.showErrorMessage("No active editor found.");
+            return;
+        }
+
+        const document = editor.document;
+        let cursorLine = editor.selection.active.line;
+        let startLine: number | null = null;
+        let endLine: number | null = null;
+
+        // Read timeout values from VS Code settings (default: 100ms and 500ms)
+        const execDelay = vscode.workspace.getConfiguration("runUntilCursor").get<number>("execDelay", 100);
+        const cursorDelay = vscode.workspace.getConfiguration("runUntilCursor").get<number>("cursorDelay", 500);
+
+        // Step 1: Find where `run()` starts
+        for (let i = 0; i < document.lineCount; i++) {
+            if (/^\s*def run\(/.test(document.lineAt(i).text)) {
+                startLine = i + 1; // Start selection after `def run():`
+                break;
+            }
+        }
+
+        if (startLine !== null) {
+            // Step 2: Find where `run()` ends (detecting **non-indented** lines)
+            for (let i = startLine; i < document.lineCount; i++) {
+                const lineText = document.lineAt(i).text;
+
+                // If we find a **non-indented line** (not empty & not a comment), assume `run()` ends here
+                if (lineText.trim().length > 0 && !lineText.startsWith(" ") && !lineText.startsWith("\t")) {
+                    endLine = i;
+                    break;
+                }
+            }
+
+            // If no non-indented line was found, assume `run()` ends at the last line
+            if (endLine === null) {
+                endLine = document.lineCount;
+            }
+        }
+
+        let executeRange: vscode.Range;
+        let shouldMoveCursor = false;
+
+        if (startLine !== null && endLine !== null) {
+            // If inside `run()`, execute up to the cursor
+            if (cursorLine >= startLine && cursorLine < endLine) {
+                executeRange = new vscode.Range(startLine, 0, cursorLine + 1, 0);
+            }
+            // If outside `run()` (either above or below), execute **the entire script** (without `if __name__ == "__main__":`)
+            else {
+                executeRange = filterOutMainBlock(document);
+                shouldMoveCursor = true;
+            }
+        } else {
+            // If `run()` is not found, execute everything (without `if __name__ == "__main__":`)
+            executeRange = filterOutMainBlock(document);
+        }
+
+        // Step 3: Apply selection and execute immediately (with an adjustable delay)
+        editor.selection = new vscode.Selection(executeRange.start, executeRange.end);
+
+        setTimeout(async () => {
+            await vscode.commands.executeCommand("jupyter.execSelectionInteractive");
+        }, execDelay); // User-configurable execution delay
+
+        // Step 4: Move cursor inside `run()` **only if the cursor was outside**
+        if (shouldMoveCursor && startLine !== null) {
+            setTimeout(() => {
+                const newPosition = new vscode.Position(startLine, 0);
+                editor.selection = new vscode.Selection(newPosition, newPosition);
+                editor.revealRange(new vscode.Range(newPosition, newPosition));
+            }, cursorDelay); // User-configurable cursor movement delay
+        }
+    });
+
+    context.subscriptions.push(disposable);
+}
+
+export function deactivate() {}
+
+/**
+ * Filters out `if __name__ == "__main__":` and its indented block.
+ */
+function filterOutMainBlock(document: vscode.TextDocument): vscode.Range {
+    let mainStart: number | null = null;
+    let mainEnd: number | null = null;
+
+    // Flexible regex for detecting variations of `if __name__ == "__main__":`
+    const mainRegex = /^\s*if\s*["']__main__["']\s*==\s*__name__\s*:|^\s*if\s*__name__\s*==\s*["']__main__["']\s*:/;
+
+    // Step 1: Find `if __name__ == "__main__":`
+    for (let i = 0; i < document.lineCount; i++) {
+        if (mainRegex.test(document.lineAt(i).text)) {
+            mainStart = i;
+            break;
+        }
+    }
+
+    if (mainStart !== null) {
+        // Step 2: Find where `if __name__ == "__main__":` block ends (first non-indented line)
+        for (let i = mainStart + 1; i < document.lineCount; i++) {
+            const lineText = document.lineAt(i).text;
+
+            // A **non-indented line** (not empty & not a comment) signals the end of the block
+            if (lineText.trim().length > 0 && !lineText.startsWith(" ") && !lineText.startsWith("\t")) {
+                mainEnd = i;
+                break;
+            }
+        }
+
+        // If no non-indented line was found, assume the block goes to the last line
+        if (mainEnd === null) {
+            mainEnd = document.lineCount;
+        }
+    }
+
+    // If we found `if __name__ == "__main__":`, remove that block
+    if (mainStart !== null && mainEnd !== null) {
+        return new vscode.Range(0, 0, mainStart, 0); // Select everything **before** `if __name__ == "__main__"`
+    } else {
+        return new vscode.Range(0, 0, document.lineCount, 0); // No `main` block, execute everything
+    }
+}
diff --git a/vscode_extensions/run-until-cursor/tsconfig.json b/vscode_extensions/run-until-cursor/tsconfig.json
new file mode 100644
index 00000000000..2824a171dbc
--- /dev/null
+++ b/vscode_extensions/run-until-cursor/tsconfig.json
@@ -0,0 +1,10 @@
+{
+    "compilerOptions": {
+        "target": "ES6",
+        "lib": ["ES6"],
+        "module": "CommonJS",
+        "outDir": "./dist",
+        "rootDir": "./src",
+        "strict": true
+    }
+}


Reviews:
user: login: lucasrodes
body: LGTM! Worked just as expected.

1) Just go first row, press `⌘`+`⏎` to run all code.
2) Go inside `run` and go to line `N` and press `⌘`+`⏎` again. This ran all lines within `run()` function until `N-1`.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-07T09:56:04Z
user: login: Marigold
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T11:02:59Z
user: login: pabloarosado
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:04:54Z
user: login: Marigold
body: Great idea! I had only one nit - to keep installing extensions separate from `make test`, but I'll leave it up to you.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T11:06:03Z
user: login: Marigold
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T11:10:21Z
user: login: pabloarosado
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T11:25:00Z
user: login: pabloarosado
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T12:38:49Z

Review comments:
diff_hunk: @@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
path: Makefile
user: login: Marigold
body: How about keeping this separate from `make test`? Installing extensions as part of "testing" is an unexpected side effect.
created_at: 2025-03-07T11:02:58Z
author_association: COLLABORATOR
diff_hunk: @@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
path: Makefile
user: login: pabloarosado
body: That's a good point, but currently we (at least I) use `make test` to set up an environment from scratch. What would you suggest otherwise?
created_at: 2025-03-07T11:04:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
path: Makefile
user: login: Marigold
body: Just to let people run both `make test` and `make install-vscode-extensions` separately. Only a few of us will be using the extension, so there's no need to install it by default. (I did the same thing with installing `uv` as part of `make test` and Lars wanted me to put it away... which I haven't done yet 😅).
created_at: 2025-03-07T11:10:21Z
author_association: COLLABORATOR
diff_hunk: @@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
path: Makefile
user: login: pabloarosado
body: The status quo is that `make test` does the installation of everything and then all tests, and `Makefile` ensures (if the code is right) that, if `code` is not installed, the extensions will not be installed, or skip them if they already are installed. So I think it makes sense for now to keep the extensions as part of the default installation (and I'll encourage others to use them).
In a separate PR we can separate installation (`make install`?) from testing. I don't see any harm in installing extensions as part of the default installation.
created_at: 2025-03-07T11:25:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,7 +64,7 @@ unittest: .venv
 	@echo '==> Running unit tests'
 	.venv/bin/pytest -m "not integration" tests
 
-test: check-formatting check-linting check-typing unittest version-tracker
+test: check-formatting check-linting check-typing unittest install-vscode-extensions version-tracker
path: Makefile
user: login: pabloarosado
body: On second thought, I think it makes sense to separate the installation of the environment and the extensions (especially when thinking of external users who may just want to try the ETL for a specific thing and are not going to develop any steps). I'll create a PR.
created_at: 2025-03-07T12:38:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: owidbot
created_at: 2025-03-04T09:02:30Z
body: <b>Quick links (staging server)</b>:
[Site Dev](http://staging-site-feature-vscode-extension-exe/) | [Site Preview](https://feature-vscode-extension-execute.owid.pages.dev/) | [Admin](http://staging-site-feature-vscode-extension-exe/admin) | [Wizard](http://staging-site-feature-vscode-extension-exe/etl/wizard/) | [Docs](http://staging-site-feature-vscode-extension-exe/etl/docs/)
|--------------------------------|----------------------------------|---|---|---|

**Login**: `ssh owid@staging-site-feature-vscode-extension-exe`

<!--grapher-start-->

<!--grapher-end-->
<!--chart-diff-start-->
<details open>
<summary><a href="http://staging-site-feature-vscode-extension-exe/etl/wizard/?page=chart-diff"><b>chart-diff</b></a>: ✅</summary>
No charts for review.
</details>
<!--chart-diff-end-->
<!--data-diff-start-->
<details>
<summary><b>data-diff</b>: ❌ Found differences</summary>

```diff
= Dataset garden/artificial_intelligence/2025-02-04/epoch_aggregates_affiliation
  = Table epoch_aggregates_affiliation
= Dataset garden/artificial_intelligence/2025-02-04/epoch_aggregates_domain
  = Table epoch_aggregates_domain
= Dataset garden/artificial_intelligence/2025-02-04/epoch_compute_intensive_countries
  = Table epoch_compute_intensive_countries
= Dataset garden/artificial_intelligence/2025-02-04/epoch_compute_intensive_domain
  = Table epoch_compute_intensive_domain
= Dataset garden/cdc/latest/measles_cases
  = Table measles_cases
    ~ Column cases (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
    ~ Column outbreaks_n (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
    ~ Column states_with_cases (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
= Dataset garden/emissions/2025-02-12/ceds_air_pollutants
  = Table ceds_air_pollutants
= Dataset garden/health/latest/measles_long_run
  = Table measles_long_run
    ~ Column case_rate (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
    ~ Column case_rate_static (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
    ~ Column cases (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
    ~ Column cases_static (changed metadata)
-       -     date_accessed: '2025-03-07'
        ?                              ^
+       +     date_accessed: '2025-03-04'
        ?                              ^
= Dataset garden/iea/2024-11-20/fossil_fuel_subsidies
  = Table fossil_fuel_subsidies
= Dataset garden/minerals/2024-07-15/minerals
  = Table minerals
= Dataset garden/oecd/2025-02-25/health_expenditure
  = Table health_expenditure
    ~ Column ppp_dollars (changed metadata)
-       - description_short: Health expenditure in <<financing_scheme.lower()>>.
+       + description_short: |-
+       +   Health expenditure in <<financing_scheme.lower()>>. <% if financing_scheme == "Government/compulsory schemes" %>
+       +   Includes government schemes, social health insurance, compulsory private insurance and compulsory medical saving accounts.
+       + 
+       +   <% elif financing_scheme == "Government schemes" %>
+       +   Includes schemes defined by law or by the government, with a separate budget set for the programme, and where a government unit has overall responsibility for it.
+       + 
+       +   <% elif financing_scheme == "Compulsory contributory health insurance schemes" %>
+       +   Includes financing arrangements to ensure access to health care for specific population groups through mandatory participation and eligibility based on the payment of health insurance contributions by or on behalf of the individuals concerned.
+       + 
+       +   <% elif financing_scheme == "Social health insurance schemes" %>
+       +   Includes financing arrangements that ensure access to health care based on a payment of a non-risk-related contribution by or on behalf of the eligible person.
+       + 
+       +   <% elif financing_scheme == "Compulsory private insurance schemes" %>
+       +   Includes financing arrangements under which all residents (or a large group of the population) are obliged to take out health insurance with a health insurance company or health insurance fund.
+       + 
+       +   <% elif financing_scheme == "Voluntary schemes/household out-of-pocket payments" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors, or where direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Voluntary health care payment schemes" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors.
+       + 
+       +   <% elif financing_scheme == "Voluntary health insurance schemes" %>
+       +   Includes financing arrangements based upon the purchase of a health insurance policy, which is not made compulsory by government.
+       + 
+       +   <% elif financing_scheme == "NPISH financing schemes" %>
+       +   Includes non-compulsory financing arrangements and programmes with non-contributory benefit entitlement that are based on donations from the general public, the government or corporations.
+       + 
+       +   <% elif financing_scheme == "Enterprise financing schemes" %>
+       +   Includes primarily arrangements where enterprises directly provide or finance health services for their employees, without the involvement of an insurance-type scheme.
+       + 
+       +   <% elif financing_scheme == "Household out-of-pocket payments" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Out-of-pocket excluding cost-sharing" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services, but excludes the share of the costs of the given services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Cost-sharing with third-party payers" %>
+       +   Includes the share of the cost of health care services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Rest of the world financing schemes (non-resident)" %>
+       +   Includes financial arrangements involving institutional units that are resident abroad, but who collect, pool resources and purchase health care goods and services on behalf of residents, without transiting their funds through a resident scheme.
+       + 
+       +   <% elif financing_scheme == "Total" %>
+       +   Includes all health care financing schemes: government, compulsory contributory insurance, voluntary insurance, household out-of-pocket payments, and rest of the world financing schemes.
+       + 
+       +   <%- endif -%>
    ~ Column ppp_dollars_per_capita (changed metadata)
-       - description_short: Health expenditure in <<financing_scheme.lower()>> divided by population.
+       + description_short: |-
+       +   Health expenditure in <<financing_scheme.lower()>> divided by population. <% if financing_scheme == "Government/compulsory schemes" %>
+       +   Includes government schemes, social health insurance, compulsory private insurance and compulsory medical saving accounts.
+       + 
+       +   <% elif financing_scheme == "Government schemes" %>
+       +   Includes schemes defined by law or by the government, with a separate budget set for the programme, and where a government unit has overall responsibility for it.
+       + 
+       +   <% elif financing_scheme == "Compulsory contributory health insurance schemes" %>
+       +   Includes financing arrangements to ensure access to health care for specific population groups through mandatory participation and eligibility based on the payment of health insurance contributions by or on behalf of the individuals concerned.
+       + 
+       +   <% elif financing_scheme == "Social health insurance schemes" %>
+       +   Includes financing arrangements that ensure access to health care based on a payment of a non-risk-related contribution by or on behalf of the eligible person.
+       + 
+       +   <% elif financing_scheme == "Compulsory private insurance schemes" %>
+       +   Includes financing arrangements under which all residents (or a large group of the population) are obliged to take out health insurance with a health insurance company or health insurance fund.
+       + 
+       +   <% elif financing_scheme == "Voluntary schemes/household out-of-pocket payments" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors, or where direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Voluntary health care payment schemes" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors.
+       + 
+       +   <% elif financing_scheme == "Voluntary health insurance schemes" %>
+       +   Includes financing arrangements based upon the purchase of a health insurance policy, which is not made compulsory by government.
+       + 
+       +   <% elif financing_scheme == "NPISH financing schemes" %>
+       +   Includes non-compulsory financing arrangements and programmes with non-contributory benefit entitlement that are based on donations from the general public, the government or corporations.
+       + 
+       +   <% elif financing_scheme == "Enterprise financing schemes" %>
+       +   Includes primarily arrangements where enterprises directly provide or finance health services for their employees, without the involvement of an insurance-type scheme.
+       + 
+       +   <% elif financing_scheme == "Household out-of-pocket payments" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Out-of-pocket excluding cost-sharing" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services, but excludes the share of the costs of the given services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Cost-sharing with third-party payers" %>
+       +   Includes the share of the cost of health care services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Rest of the world financing schemes (non-resident)" %>
+       +   Includes financial arrangements involving institutional units that are resident abroad, but who collect, pool resources and purchase health care goods and services on behalf of residents, without transiting their funds through a resident scheme.
+       + 
+       +   <% elif financing_scheme == "Total" %>
+       +   Includes all health care financing schemes: government, compulsory contributory insurance, voluntary insurance, household out-of-pocket payments, and rest of the world financing schemes.
+       + 
+       +   <%- endif -%>
    ~ Column share_expenditure (changed metadata)
-       - description_short: Health expenditure in <<financing_scheme.lower()>> divided by the total health expenditure, expressed as
-       -   a percentage.
+       + description_short: |-
+       +   Health expenditure in <<financing_scheme.lower()>> divided by the total health expenditure, expressed as a percentage. <% if financing_scheme == "Government/compulsory schemes" %>
+       +   Includes government schemes, social health insurance, compulsory private insurance and compulsory medical saving accounts.
+       + 
+       +   <% elif financing_scheme == "Government schemes" %>
+       +   Includes schemes defined by law or by the government, with a separate budget set for the programme, and where a government unit has overall responsibility for it.
+       + 
+       +   <% elif financing_scheme == "Compulsory contributory health insurance schemes" %>
+       +   Includes financing arrangements to ensure access to health care for specific population groups through mandatory participation and eligibility based on the payment of health insurance contributions by or on behalf of the individuals concerned.
+       + 
+       +   <% elif financing_scheme == "Social health insurance schemes" %>
+       +   Includes financing arrangements that ensure access to health care based on a payment of a non-risk-related contribution by or on behalf of the eligible person.
+       + 
+       +   <% elif financing_scheme == "Compulsory private insurance schemes" %>
+       +   Includes financing arrangements under which all residents (or a large group of the population) are obliged to take out health insurance with a health insurance company or health insurance fund.
+       + 
+       +   <% elif financing_scheme == "Voluntary schemes/household out-of-pocket payments" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors, or where direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Voluntary health care payment schemes" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors.
+       + 
+       +   <% elif financing_scheme == "Voluntary health insurance schemes" %>
+       +   Includes financing arrangements based upon the purchase of a health insurance policy, which is not made compulsory by government.
+       + 
+       +   <% elif financing_scheme == "NPISH financing schemes" %>
+       +   Includes non-compulsory financing arrangements and programmes with non-contributory benefit entitlement that are based on donations from the general public, the government or corporations.
+       + 
+       +   <% elif financing_scheme == "Enterprise financing schemes" %>
+       +   Includes primarily arrangements where enterprises directly provide or finance health services for their employees, without the involvement of an insurance-type scheme.
+       + 
+       +   <% elif financing_scheme == "Household out-of-pocket payments" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Out-of-pocket excluding cost-sharing" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services, but excludes the share of the costs of the given services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Cost-sharing with third-party payers" %>
+       +   Includes the share of the cost of health care services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Rest of the world financing schemes (non-resident)" %>
+       +   Includes financial arrangements involving institutional units that are resident abroad, but who collect, pool resources and purchase health care goods and services on behalf of residents, without transiting their funds through a resident scheme.
+       + 
+       +   <% elif financing_scheme == "Total" %>
+       +   Includes all health care financing schemes: government, compulsory contributory insurance, voluntary insurance, household out-of-pocket payments, and rest of the world financing schemes.
+       + 
+       +   <%- endif -%>
-       - description_key:
-       -   - |-
-       -     <% if financing_scheme == "Government/compulsory schemes" %>
-       -     This data includes government schemes, social health insurance, compulsory private insurance and compulsory medical saving accounts.
-       - 
-       -     <%- elif financing_scheme == "Government schemes" %>
-       -     This data includes schemes defined by law or by the government, with a separate budget set for the programme, and where a government unit has overall responsibility for it.
-       - 
-       -     <%- elif financing_scheme == "Compulsory contributory health insurance schemes" %>
-       -     This data includes financing arrangements to ensure access to health care for specific population groups through mandatory participation and eligibility based on the payment of health insurance contributions by or on behalf of the individuals concerned.
-       - 
-       -     <%- elif financing_scheme == "Social health insurance schemes" %>
-       -     This data includes financing arrangements that ensure access to health care based on a payment of a non-risk-related contribution by or on behalf of the eligible person.
-       - 
-       -     <%- elif financing_scheme == "Compulsory private insurance schemes" %>
-       -     This data includes financing arrangements under which all residents (or a large group of the population) are obliged to take out health insurance with a health insurance company or health insurance fund.
-       - 
-       -     <%- elif financing_scheme == "Voluntary schemes/household out-of-pocket payments" %>
-       -     This data includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors, or where direct payment is made at the time of the purchase of goods or use of services.
-       - 
-       -     <%- elif financing_scheme == "Voluntary health care payment schemes" %>
-       -     This data includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors.
-       - 
-       -     <%- elif financing_scheme == "Voluntary health insurance schemes" %>
-       -     This data includes financing arrangements based upon the purchase of a health insurance policy, which is not made compulsory by government.
-       - 
-       -     <%- elif financing_scheme == "NPISH financing schemes" %>
-       -     This data includes non-compulsory financing arrangements and programmes with non-contributory benefit entitlement that are based on donations from the general public, the government or corporations.
-       - 
-       -     <%- elif financing_scheme == "Enterprise financing schemes" %>
-       -     This data includes primarily arrangements where enterprises directly provide or finance health services for their employees, without the involvement of an insurance-type scheme.
-       - 
-       -     <%- elif financing_scheme == "Household out-of-pocket payments" %>
-       -     This data includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services.
-       - 
-       -     <%- elif financing_scheme == "Out-of-pocket excluding cost-sharing" %>
-       -     This data includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services, but excludes the share of the costs of the given services covered by the scheme.
-       - 
-       -     <%- elif financing_scheme == "Cost-sharing with third-party payers" %>
-       -     This data includes the share of the cost of health care services covered by the scheme.
-       - 
-       -     <%- elif financing_scheme == "Rest of the world financing schemes (non-resident)" %>
-       -     This data includes financial arrangements involving institutional units that are resident abroad, but who collect, pool resources and purchase health care goods and services on behalf of residents, without transiting their funds through a resident scheme.
-       - 
-       -     <%- elif financing_scheme == "Total" %>
-       -     This data includes all health care financing schemes: government, compulsory contributory insurance, voluntary insurance, household out-of-pocket payments, and rest of the world financing schemes.
-       - 
-       -     <%- endif %>
    ~ Column share_gdp (changed metadata)
-       - description_short: Health expenditure in <<financing_scheme.lower()>> divided by gross domestic product, expressed as a percentage.
+       + description_short: |-
+       +   Health expenditure in <<financing_scheme.lower()>> divided by gross domestic product, expressed as a percentage. <% if financing_scheme == "Government/compulsory schemes" %>
+       +   Includes government schemes, social health insurance, compulsory private insurance and compulsory medical saving accounts.
+       + 
+       +   <% elif financing_scheme == "Government schemes" %>
+       +   Includes schemes defined by law or by the government, with a separate budget set for the programme, and where a government unit has overall responsibility for it.
+       + 
+       +   <% elif financing_scheme == "Compulsory contributory health insurance schemes" %>
+       +   Includes financing arrangements to ensure access to health care for specific population groups through mandatory participation and eligibility based on the payment of health insurance contributions by or on behalf of the individuals concerned.
+       + 
+       +   <% elif financing_scheme == "Social health insurance schemes" %>
+       +   Includes financing arrangements that ensure access to health care based on a payment of a non-risk-related contribution by or on behalf of the eligible person.
+       + 
+       +   <% elif financing_scheme == "Compulsory private insurance schemes" %>
+       +   Includes financing arrangements under which all residents (or a large group of the population) are obliged to take out health insurance with a health insurance company or health insurance fund.
+       + 
+       +   <% elif financing_scheme == "Voluntary schemes/household out-of-pocket payments" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors, or where direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Voluntary health care payment schemes" %>
+       +   Includes domestic pre-paid financing schemes under which the access to health services is at the discretion of private actors.
+       + 
+       +   <% elif financing_scheme == "Voluntary health insurance schemes" %>
+       +   Includes financing arrangements based upon the purchase of a health insurance policy, which is not made compulsory by government.
+       + 
+       +   <% elif financing_scheme == "NPISH financing schemes" %>
+       +   Includes non-compulsory financing arrangements and programmes with non-contributory benefit entitlement that are based on donations from the general public, the government or corporations.
+       + 
+       +   <% elif financing_scheme == "Enterprise financing schemes" %>
+       +   Includes primarily arrangements where enterprises directly provide or finance health services for their employees, without the involvement of an insurance-type scheme.
+       + 
+       +   <% elif financing_scheme == "Household out-of-pocket payments" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services.
+       + 
+       +   <% elif financing_scheme == "Out-of-pocket excluding cost-sharing" %>
+       +   Includes financing schemes under which direct payment is made at the time of the purchase of goods or use of services, but excludes the share of the costs of the given services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Cost-sharing with third-party payers" %>
+       +   Includes the share of the cost of health care services covered by the scheme.
+       + 
+       +   <% elif financing_scheme == "Rest of the world financing schemes (non-resident)" %>
+       +   Includes financial arrangements involving institutional units that are resident abroad, but who collect, pool resources and purchase health care goods and services on behalf of residents, without transiting their funds through a resident scheme.
+       + 
+       +   <% elif financing_scheme == "Total" %>
+       +   Includes all health care financing schemes: government, compulsory contributory insurance, voluntary insurance, household out-of-pocket payments, and rest of the world financing schemes.
+       + 
+       +   <%- endif -%>


Legend: +New  ~Modified  -Removed  =Identical  Details
Hint: Run this locally with etl diff REMOTE data/ --include yourdataset --verbose --snippet
```

Automatically updated datasets matching _weekly_wildfires|excess_mortality|covid|fluid|flunet|country_profile|garden/ihme_gbd/2019/gbd_risk_ are not included
</details>
<!--data-diff-end-->

_Edited: 2025-03-07 08:18:57 UTC_
_Execution time: 37.60 seconds_
author_association: CONTRIBUTOR
user: login: Marigold
created_at: 2025-03-05T08:37:27Z
body: Good luck with this! It'd be amazing if you could make it work. My dream was to be able to set a breakpoint in VSCode and open the debugger in the interactive python window. Users have been [asking for it](https://github.com/microsoft/vscode-jupyter/issues/1278) for years, but there's no progress 😢 
author_association: COLLABORATOR
user: login: pabloarosado
created_at: 2025-03-07T08:49:18Z
body: Hey @Marigold and @lucasrodes, do you want to give it a try? Simply switch to this branch, and run `make test`. Then go to any etl data step and hit `cmd+Enter`. Then move some lines down inside the `run()` function and run `cmd+Enter` again. Please let me know if that works as expected, thanks!
author_association: CONTRIBUTOR
user: login: pabloarosado
created_at: 2025-03-07T10:00:33Z
body: Thanks @lucasrodes ! I'm happy it works. But note that the extension should execute all code until the current line (included).

I've noticed now that, on any interactive window, if I type, e.g. `1+1` and hit `shift+Enter`, it opens a new interactive window (which is absurd). Is that happening to you too? If so, it could be an issue with the extension. Otherwise, I just messed up something in my local settings. Please let me know if you have the same issue, thanks!

author_association: CONTRIBUTOR
user: login: Marigold
created_at: 2025-03-07T10:59:34Z
body: `1+1` and hitting shift + enter works fine for me.

The extension is super neat and works as expected. I'd merge it as it is and see how it works for us. I'm personally still in favour of playground notebooks on the side (mainly to have something to come back to), but perhaps this will change my mind!
author_association: COLLABORATOR