This post was originally posted on Feb 10, 2019 to https://www.codependentcodr.com/visual-studio-code-tasks-and-split-terminals.html#visual-studio-code-tasks-and-split-terminals
So as a big Visual Studio Code fan, I've long made use of the tasks feature. The most recent (January 2019, 1.31) update added a cool new feature related to this that I've been waiting for for some time. I thought I'd do a little write up about this and how I use tasks with VS Code, particularly as a Pythonista.
Task Basics
As a starting point, to give a basic idea of what tasks are, they're effectively little shortcuts to terminal commands that you can trigger from within VS Code. They're commonly used for things like triggering build tasks, or starting up a local dev server, etc. The thing that makes them nice is that they can be triggered from the command pallette much like normal VS Code commands. For example, when working on this blog, I'll use a task to fire up a local dev server to test out content before committing/pushing it. It looks something like this:
At this point I can then go to http://localhost:8000 and see the content I've been working on. Handy. To create a task, you open up the command pallette and pick "Tasks: Configure Task" and you'll be prompted with some default template tasks, or the option to "Create tasks.json file from template" which gives you total control and is the option I use.
A tasks.json
file contains a number of JSON blobs which define your tasks. They look something like:
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Run Server",
"type": "shell",
"command": "source $(dirname ${config:python.pythonPath})/activate && make devserver"
},
]
}
This is the definition for my run local dev server that I showed in the video. You can have as many tasks as you want, the tasks
property is just a list of these definitions. The full list of properties and options are
in Microsoft's excellent docs.
Common Python Tasks
So now that we have an idea of what tasks are, what are some of the neat things you can do with them, particularly from the perspective of a Python developer? These are some of the common ones I set up, most of which are Django related, since much of my day job is working in that framework:
Running a Dev Server
{
"label": "Run Server",
"type": "shell",
"command": "${config:python.pythonPath} manage.py runserver --noreload",
},
This is basically the analogy to the task I showed previously, just using Django's runserver
command. One thing to note about this: note that the label is the same as the one for my blog project. tasks.json
files are stored per-project, but one neat thing is you can assign a hotkey to a given task. In my keybindings.json
I
have:
{
"key": "cmd+shift+r",
"command": "workbench.action.tasks.runTask",
"args": "Run Server"
},
This allows me to start a local dev server by simply hitting a hotkey, and so long as I name that "start up a local dev environment" task the same on each project, it's the same keystroke to start up a local dev environment.
Hitting a Health Check URL
{
"label": "Healthcheck (requires running server)",
"type": "shell",
"command": "curl http://127.0.0.1:6100/health"
},
Once I have a local server running, it's handy to be able to quickly hit the
health check url (you added a health check to your API right?). Again, this is small, but handy as it saves me the trouble of tabbing over to a terminal window, typing out the curl
command, realizing that this project runs on a different port, going to look that up, etc, etc, etc.
Running Unit Tests
{
"label": "Run Unit Tests",
"type": "shell",
"group": {
"kind": "test",
"isDefault": true
},
"command": "${config:python.pythonPath} -m pytest -rxXs --ds=projectname.settings.local_test --random-order"
},
Whenever possible I use pytest for running my unit tests. Normally this is run from the command line as something like pytest <name of directory containing tests>
. The problem though is that pytest gets installed to a virtual environment, so how do I give the full path to the virtual env without making the task machine specific? The answer is I run it as a module and just use the config:python.pythonPath
variable to reference whatever the current Python environment is. The other options are some common ones I feed to pytest, ex the --ds
switch is for specifying the DJANGO_SETTINGS_MODULE
environment variable. --random-order
uses the Pytest Random Order plugin to run the tests in a random order on each test run (which has discovered bugs in my code/tests).
I also set a hotkey for this task:
{
"key": "shift+cmd+f11",
"command": "workbench.action.tasks.test"
},
This makes use of the kind
property of the task definition.
Update Dependencies
{
"label": "Update Python Dependencies",
"type": "shell",
"command": "${config:python.pythonPath} -m pip install -r requirements.txt --upgrade && ${config:python.pythonPath} -m pip install -r requirements-dev.txt --upgrade"
},
I still use requirements.txt
files (I really should spend the time to learn pipenv, but alas). With this task I can quickly update all my project's dependencies. I also separate out my project's dependencies and my project's dev dependencies (think things like pytest or pylint) into separate files. The reason for this is that I can then let my dev dependencies "float", and most projects I work on also build a Docker image at the end of the day, so separating the dependencies allows me to only install the dependencies needed for running the project into the Docker image, which cuts down on image size.
Many Many More
This is just scratching the surface, any time I find myself commonly running commands in a terminal window on a project, I'll spend the minute or so to turn that into a VS Code task.
Lastly, one of the key points here is that I do essentially these same tasks on any project I work on and I just tweak the specific commands for the particular project. This creates a common/familiar workflow for me regardless of if it's a Django project, Flask, or even an entirely different tech (I had a Java project with a REST API and I created many of the same tasks for that).
New Tricks
As mentioned, in the January 2019 update they added a new feature related to tasks that I'm a huge fan of: Task Output Split Terminals
This allows you to have a task spawned into a split terminal window to another (already running) task. This is really handy when you have say a task for running the dev server and another task for say tailing the log file of that server as you can have them appear side-by-side in the integrated terminal.
This was particularly useful for a project I work on at work where I have a Django-based server, which speaks to another local dev server via a socket connection. Previously I had tasks set up for both of these, and I'd have to fire up each one individually, and switch between multiple terminal windows to see the output of each. Now I can have them show up side by side in one view. The way this works is by sharing the same group
property in the task's presentation
property:
{
"label": "Run Server",
"type": "shell",
"command": "${config:python.pythonPath} manage.py runserver --noreload",
"presentation": {
"group": "groupServerStuff"
}
},
All tasks with the same group will open up as another split terminal pane in the same terminal window. Very nice.
This got me to thinking though: rather than start each task individually, is there a way to have tasks "call" or "spawn" other tasks? And as it turns out there is:
{
"label": "Run Server",
"dependsOn": [
"Run TCP Server",
"Run Django Server",
"Tail Log File"
]
},
{
"label": "Run Django Server",
"type": "shell",
"command": "${config:python.pythonPath} manage.py runserver --noreload",
"presentation": {
"group": "groupServerStuff"
}
},
{
"label": "Run TCP Server",
"type": "shell",
"command": "${config:python.pythonPath} scripts/tcp_server.py",
"presentation": {
"group": "groupServerStuff"
}
},
{
"label": "Tail Log File",
"type": "shell",
"command": "tail -f /tmp/logfile.txt",
"presentation": {
"group": "groupServerStuff"
}
},
Check out that Run Server
task -- it spawns three other tasks I have defined: "Run Django Server" (which was my previous "Run Server" task), "Run TCP Server" (the simulated socket server), and "Tail Log File" which just tails the logfile that Django is logging to.
And of course, because it's called Run Server
the same hotkey I defined previously will spawn up a new terminal window split 3-ways with these tasks running. All with a single keystroke. That's pretty powerful stuff!
In any case, I hope this was a useful overview of Tasks in VS Code. Have you come up with any creative uses for them? Lemme know in the comments!
Top comments (5)
Kinda forgot about tasks in Code - perfect reminder to start using them.
I enjoyed this post. I particularly like how you have arranged your tasks to do the same kind of thing with the same keybinging, but in the right way for context. Like polymorphic keys. It's a great idea and I'm going to do something like it
Nice, glad it was useful, and thanks for the feedback!
I'd been trying to figure out how to spawn multiple tasks with one keystroke for several hours. I found your article after I found the "Launch task directly into split terminal #47265" github discussion. This is exactly what I needed! Thanks! Woot!
Awesome, glad it was useful!