45

I have a local folder that I use as a scratch pad for multiple little sample and toy pieces of code. I store a host of python, C++, shell scripts etc. in this directory.

I'm using Visual Studio Code (on OS X) and am looking into its tasks to run/compile the code snippets without having to switch to a terminal.

For example, I found this following task will run python on the currently open file.

// A task runner that runs a python program
{
    "version": "0.1.0",
    "command": "/usr/bin/python",
    "args": ["${file}"]
}

This task will use python as the task runner irrespective of the type of file I'm currently editing.

How do I implement a task to run a command based on the file type (or select between multiple commands)? I.e. if I'm editing a C++ file, it will run clang++.

  • If I can't do it based on file type; are there any alternatives to this?
  • An alternative would be; are multiple commands supported?
Gama11
  • 31,714
  • 9
  • 78
  • 100
Niall
  • 30,036
  • 10
  • 99
  • 142

7 Answers7

49

You can always use bash as your task runner and then assign arbitrary terminal commands as your tasks.

{
    "version": "0.1.0",
    "command": "bash",
    "isShellCommand": true,
    "showOutput": "always",
    "args": [
        "-c"
    ],
    "tasks": [
        {
            "taskName": "My First Command",
            "suppressTaskName": true,
            "isBuildCommand": true,
            "args": ["echo cmd1"]
        },
        {
            "taskName": "My Command Requiring .bash_profile",
            "suppressTaskName": true,
            "args": ["source ~/.bash_profile && echo cmd2"]
        },
        {
            "taskName": "My Python task",
            "suppressTaskName": true,
            "args": ["/usr/bin/python ${file}"]
        }
    ]
}

A few notes on what is happening here:

  • Using bash -c for all tasks by putting it in args list of the command so that we can run arbitrary commands. The echo statements are just examples but could be anything executable from your bash terminal.
  • The args array will contain a single string to be passed to bash -c (separate items would be treated as multiple arguments to the bash command and not the command associated with the -c arg).
  • suppressTaskName is being used to keep the taskName out of the mix
  • The second command shows how you can load your ~/.bash_profile if you need anything that it provides such as aliases, env variables, whatever
  • Third command shows how you could use your Python command you mentioned

This will not give you any sort of file extension detection, but you can at least use cmd+p then type "task " to get a list of your tasks. You can always mark your 2 most common commands with isBuildCommand and isTestCommand to run them via cmd+shift+b or cmd+shift+t respectively.

This answer has some helpful information that might be useful to you as well.

Gama11
  • 31,714
  • 9
  • 78
  • 100
bingles
  • 11,582
  • 10
  • 82
  • 93
  • I've come back to this and I really like the idea of using `bash -c` as the command runner. This suits me quite nicely. – Niall Jan 19 '16 at 13:35
  • I've created a variant of what you mention above to compile a simple C++ program. Unfortunately I cannot paste it in a comment. However what I notice is that the output of the task is not shown in the output pane. For example if my task executes "bash -c helloworld" there is no "Hello world" appearing in the output pane. Trying to figure out how to do that right now. – andrea Jan 20 '16 at 05:36
  • And this is what I converged to: https://github.com/ataiya/hellovsc Notice for some reason I can see the computer/run output _only_ when I do "echoCommand": false, which is particularly counter-intuitive! Also it's quite terrible you have no idea when the task has finished executing. – andrea Jan 20 '16 at 05:56
  • I've not worked with echoCommand so not sure what the nuance is there. As for knowing when your task is complete, you can always concatenate && echo done to the end of your command. "args": ["g++ -v hello.cpp -o hello && echo done"] – bingles Jan 20 '16 at 12:43
18

The simplest way would be to add them separated by ; (or &&) in a shell:

tasks.json:

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "test",
            "type": "shell",
            "command": "cd ~/dev/xxxx;source ~/dev/yyyy;ls",
        }
    ]
}
Adriel Jr
  • 2,451
  • 19
  • 25
  • 2
    That works for a few short commands, but unfortunately, you cannot have multi-line strings in json, so you can't add line breaks for visual readability if there are many commands. – wisbucky Jan 12 '22 at 22:58
11

You can use compound tasks to run multiple commands https://code.visualstudio.com/docs/editor/tasks#_compound-tasks

marcosalpereira
  • 319
  • 3
  • 5
10

Recent changes to the tasks.json seem to have made a command available for each of the tasks listed. See https://code.visualstudio.com/docs/editor/tasks which makes a lot of this moot.


This answer was originally aimed at a more complex solution, but the simple shell runner task format as presented in the accepted answer proved more useful. See below for what that looks like now.


The limitation here is that VS Code is limited to a single high level build task/command for a given workspace. Multiple sub-tasks are allowed, but they are limited to using the top level "command" but can provide different "arguments". This would be well suited to an environment that uses a build system akin to make, ant or msbuild. E.g.;

{
    "version": "0.1.0",
    "command": "make", // command must appear here
    "tasks" : [
        {
            "taskName": "clean",
            "suppressTaskName": false, // false by default
            //"command": "somethingelse", // not valid here
            "args": ["${file}"] // if required
        },
        {
            "taskName": "install"
            // ...
        }
    ]
}

Two alternatives are available;

  • Have a custom script attempt to run the compile/execution solely given the arguments in task.json.

     -- the shell file would be a simple
     "$@" # run what I get
     -- the tasks.json
     "args": ["clang++", "-std=c++14", "-O2", "${file}"]
    

Getting the exectuable to run (./a.out) was more effort. Simply adding it as an argument didn't work, the shell script was required to execute it if it was there.

  • Shell out the switching and the execution of the output to a custom script, given the file extension and filename. This proved easier to implement and offered more control in the shell script.

     {
         "version": "0.1.0",
         "isShellCommand": true,
         "taskName": "generic build",
         "showOutput": "always",
         "args": ["${fileExtname}", "${file}"]
         "command": "./.vscode/compileme.sh", // expected in the "local settings" folder
         //"command": "~/compileme.sh", // if in HOME folder
     }
    

And the shell script, compileme.sh;

    #!/bin/sh
    # basic error checking not shown...
    echo "compilation being executed with the arguments;"
    echo "$@"
    filetype=$1
    file=$2
    if [ $filetype = ".cpp" -o $filetype = ".cxx" ] ; then 
        clang++ -std=c++14 -Wall -Wextra -pedantic -pthread $file && ./a.out
    elif [ $filetype = ".c" ]
        then 
        clang -std=c11 -Wall -Wextra -pedantic -pthread $file && ./a.out
    elif [ $filetype = ".sh" ]
        then
        $file
    elif [ $filetype = ".py" ]
        then
        python $file
    else
        echo "file type not supported..."
        exit 1
    fi

Given the options listed above, the second option is preferable. This implementation works on OS X, but it could be easily ported to Linux and Windows as needed. I'll keep on eye on this and try track changes to the VS Code build tasks, file based builds or support for multiple commands could be a welcome addition.


My tasks.json supports a few runners, and a default for the build that prints message as a reminder. It uses the shell as the runner and now looks like...

{
    "version": "0.1.0",
    "isShellCommand": true,
    "taskName": "GenericBuild",
    "showOutput": "always",
    "command": "sh",
    "suppressTaskName": false,
    "args": ["-c"],
    "tasks": [
        {
            "taskName": "no build",
            "suppressTaskName": true,
            "isBuildCommand": true,
            "args": [
                "echo There is no default build task, run a task by name..."
            ]
        },
        {
            "taskName": "cpp",
            "suppressTaskName": true,
            "args": [
                "clang++ -std=c++14 -Wall -Wextra -pedantic -pthread \"${file}\" && ./a.out"
            ]
        },
        {
            "taskName": "shell",
            "suppressTaskName": true,
            "args": [
                "\"${file}\""
            ]
        },
        {
            "taskName": "python",
            "suppressTaskName": true,
            "args": [
                "python \"${file}\""
            ]
        },
        {
            "taskName": "c",
            "suppressTaskName": true,
            "args": [
                "clang -std=c11 -Wall -Wextra -pedantic -pthread \"${file}\" && ./a.out"
            ]
        }
    ]
}
Niall
  • 30,036
  • 10
  • 99
  • 142
1

You could write and run a custom script file instead of python etc. directly. In the script file you would extract the file extension in order to call python, clang or whatever the compiler/translator needed may be.

So your task file would look like this;

// A task runner that runs a program
{
   "version": "0.1.0",
   "command": "${workspaceRoot}\\runProgram.sh",
   "args": ["${file}"]
}
KyleMit
  • 30,350
  • 66
  • 462
  • 664
Wosi
  • 41,986
  • 17
  • 75
  • 82
1

Something also to note from Adriel's answer is the ability to pass a list to the command keyword. Paired with the shell label, you can pass multiple CLI commands in a human-readable format without the need for compounding tasks.

Just be aware that elements of the list are delimited by spaces and must be properly suffixed depending on how you want your shell to interpret them. For example, you can place semicolons so a bash shell separates them into separate commands:

"version": "2.0.0",
"tasks": [
    {
        "label": "build",
        "type": "shell",
        "command": [
            "python -m module arg1 arg2;",
            "cp outputfile.json someproject/results;",
            "cp version.txt someproject/version"
        ]
    }
]
n8atnite
  • 11
  • 1
-1

I made this script.

It requires that you install python IDLE in your environment. This will open the IDLE and run your python file each time you run your task (CTRL+Shift+B).

{
    "version": "0.1.0",             
    "command": "/usr/bin/idle",
    "isShellCommand": false,
    "showOutput": "never",
    "args": ["-r","${file}"]    
} 
Community
  • 1
  • 1
MrAlex6204
  • 167
  • 7