How to create a "terminal configuration macro" in VS Code? Open and split multiple terminals easily

Viewed 949

~400 times by year 1 :

  • open VS Code
  • open one terminal
  • divide the terminal into 2 parts
  • run "npm run hot" (first split)
  • leave the other split terminal empty
  • open a second terminal (with "+" button)
  • split it into 3 parts
  • run "php artisan websockets:serve" (first part)
  • run "php artisan queue:words" (second part)
  • run "maidev --ip=localhost" (third part)
  • and finally I can start to work

I'm sure I am not alone with this problem. Would anyone have a method to optimize that with one command or configuration ?

Thanks for your help !

3 Answers

Use this setting:

"terminal.integrated.windowsEnableConpty": false // true is the default


In tasks.json:

{
  "label": "Run 2 terminals from tasks",
  
  "dependsOrder": "sequence",  // or parallel
  
  "dependsOn": [
    "OpenTerminal1",
    "RunInTerminal1",
    "RenameTerminal1",   // if you care to rename each terminal
    "SplitTerminal1",

    "OpenTerminal2",
    "RunInTerminal2a",
    "RenameTerminal2a",   // if you care to rename each terminal

    "SplitTerminal2",
    "RunInTerminal2b",
    "RenameTerminal2b",   // if you care to rename each terminal

    "SplitTerminal2",
    "RunInTerminal2c",
    "RenameTerminal2c",   // if you care to rename each terminal
  ],
  // "runOptions": { "runOn": "folderOpen" }  // or trigger with keybinding
},

That is the "master" task which calls all the constituent tasks, which are:

{
  "label": "OpenTerminal1",
  "command": "${command:workbench.action.terminal.new}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RunInTerminal1",
  "command": "${input:runTerminal1}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RenameTerminal1",
  "command": "${input:renameTerminal1}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "SplitTerminal1",
  "command": "${command:workbench.action.terminal.split}",
  "type": "shell",
  "problemMatcher": []
},
// ------------------------------------------------------------------
{
  "label": "OpenTerminal2",
  "command": "${command:workbench.action.terminal.new}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RunInTerminal2a",
  "command": "${input:runTerminal2a}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RenameTerminal2a",
  "command": "${input:renameTerminal2a}",
  "type": "shell",
  "problemMatcher": []
},
// ------------------------------------------------------------------
{
  "label": "SplitTerminal2",
  "command": "${command:workbench.action.terminal.split}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RunInTerminal2b",
  "command": "${input:runTerminal2b}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RenameTerminal2b",
  "command": "${input:renameTerminal2b}",
  "type": "shell",
  "problemMatcher": []
},
// ------------------------------------------------------------------
{
  "label": "RunInTerminal2c",
  "command": "${input:runTerminal2c}",
  "type": "shell",
  "problemMatcher": []
},
{
  "label": "RenameTerminal2c",
  "command": "${input:renameTerminal2c}",
  "type": "shell",
  "problemMatcher": []
},

You might think that is enough but you can see that task require args (what to send to the terminals) so that I had to use this form:

"command": "${input:runTerminal2c}",

so that that input can provide the needed arguments and commands. Those go into the inputs section of your tasks file like so:

"inputs": [   

 {
   "id": "openTerminal1",      
   "type": "command",
   "command": "workbench.action.terminal.new",
 },
 {
   "id": "runTerminal1",
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "npm run test\u000D"
   }
 },
 {
   "id": "renameTerminal1",      
   "type": "command",
   "command": "workbench.action.terminal.renameWithArg",
   "args": {
     "name": "npm run hot"
   }
 },

 {
   "id": "openTerminal2",      
   "type": "command",
   "command": "workbench.action.terminal.new",
 },
 {
   "id": "runTerminal2a",      
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "php artisan websockets:serve"
   }
 },
 {
   "id": "renameTerminal2a",
   "type": "command",
   "command": "workbench.action.terminal.renameWithArg",
   "args": {
     "name": "websockets:serve"
   }
 },
 {
   "id": "runTerminal2b",
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "php artisan queue:words"
   }
 },
 {
   "id": "renameTerminal2b",
   "type": "command",
   "command": "workbench.action.terminal.renameWithArg",
   "args": {
     "name": "queue:words"
   }
 },
 {
   "id": "runTerminal2c",
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "maidev"
   }
 },
 {
   "id": "renameTerminal2c",
   "type": "command",
   "command": "workbench.action.terminal.renameWithArg",
   "args": {
     "name": "maidev"
   }
 }

Again maybe you don't care about all the renaming the terminal commands and can eliminate those. Here, I just sent your command to the terminal:

 {
   "id": "runTerminal2c",
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "maidev"
   }
 },

but did not automatically start them. To have them start immediately, add the return unicode to the text that is sent to the terminal like:

 {
   "id": "runTerminal2c",
   "type": "command",
   "command": "workbench.action.terminal.sendSequence",
   "args": {
     "text": "maidev --ip=localhost"
   }
 },

demo of two tasks opening split terminals


And finally a keybinding to trigger the master task (if you don't use the run on folder open option):

{
  "key": "alt+z",
  "command": "workbench.action.tasks.runTask",
  "args": "Run 2 terminals from tasks"
},

I prefer the macro version - it is much shorter and less prone to set-up error. Both are very fast.

I'll make this two answers as one version is particularly long. One answer uses a macro extension to help solve this and the other uses only tasks.

For both solutions, use this setting:

"terminal.integrated.windowsEnableConpty": false // true is the default

There is a nasty unsolved bug affecting Winpty and vscode when you try to delete a terminal opened by these methods.


Using the macro extension multi-command. This goes into your tasks.json:

 {
    "label": "Run 2 terminals with macro",
    "dependsOrder": "sequence", // or parallel
    "dependsOn": [
      "terminal1",
      "terminal2"
    ],
    // "runOptions": { "runOn": "folderOpen" }
  },
  {
    "label": "terminal1",
    "command": "${command:multiCommand.startFirstTerminal}"
  },
  {
    "label": "terminal2", 
    "command": "${command:multiCommand.startSecondTerminal}"
  },

The two tasks are run by the macro. This goes into your settings.json:

"multiCommand.commands": [

{
  "command": "multiCommand.startFirstTerminal",
  "sequence": [
    "workbench.action.terminal.newInActiveWorkspace",
    {
      "command": "workbench.action.terminal.sendSequence",
      "args": {
        "text": "npm run test\u000D"
      }
    },
    {
      "command": "workbench.action.terminal.renameWithArg",
      "args": {
        "name": "npm run hot"
      }
    },
     "workbench.action.terminal.split",
  ]
},
{
  "command": "multiCommand.startSecondTerminal",
  "sequence": [
    "workbench.action.terminal.newInActiveWorkspace",
    {
      "command": "workbench.action.terminal.renameWithArg",
      "args": {
        "name": "websockets:serve"
      }
    },
    {
      "command": "workbench.action.terminal.sendSequence",
      "args": {
        "text": "ls -lrt\u000D"
      }
    },
    "workbench.action.terminal.split",
    {
      "command": "workbench.action.terminal.renameWithArg",
      "args": {
        "name": "queue:words"
      }
    },
    {
      "command": "workbench.action.terminal.sendSequence",
      "args": {
        "text": "ls -lrt\u000D"
      }
    },
    "workbench.action.terminal.split",
    {
      "command": "workbench.action.terminal.renameWithArg",
      "args": {
        "name": "maidev"
      }
    },
    {
      "command": "workbench.action.terminal.sendSequence",
      "args": {
        "text": "ls -lrt\u000D"
      }
    },
  ]
 }
]

I renamed the terminals to match your commands - perhaps you don't care about that and can eliminate the renameWithArg sections to shorten the whole thing.

I couldn't test with your php setup, so I substituted ls -lrt\u000D running in each terminal. Just substitute your

   {
      "command": "workbench.action.terminal.sendSequence",
      "args": {
        "text": "php artisan websockets:serve\u000D"
      }
    },

for example. The \u000D is a return so the command runs immediately. Now you can either use the "runOptions": { "runOn": "folderOpen" } option so the master task runs when the workspace opens or assign a keybinding to the master task like

{
  "key": "alt+z",   // or watever you want
  "command": "workbench.action.tasks.runTask",
  "args": "Run 2 terminals with macro"
},

Okay, a day after answering this with other two answers, I saw this extension: Restore Terminals at this other question https://stackoverflow.com/a/62595681/836330

This extension seems to work quite well. Put this into your settings.json:

  "restoreTerminals.runOnStartup": false,   // true is the default
          // set to false if using a keybinding or the command palette

  "restoreTerminals.terminals": [
    {
      "splitTerminals": [
        {
          "name": "server",
          "commands": [
            "npm run test"
          ]
        },
        {
          "name": "empty",
        }
      ]
    },
    {
      "splitTerminals": [
        {
          "name": "websockets",
          "commands": [
            "ls -l"
          ]
        },
        {
          "name": "queue",
          "commands": [
            "cd zip",
            "gulp sass"
          ]
        },
        {
          "name": "maidev",
          "commands": [
            "cd zip-multiple",
            "gulp"
          ]
        }
      ]
    }
  ],

And a keybinding:

{
  "key": "shift+alt+t",    // whatever keybinding if you wish
  "command": "restore-terminals.restoreTerminals",
},

If on Windows, I would still use the setting:

 "terminal.integrated.windowsEnableConpty": false

as the same issue occurs when using this extension as any other method to open and write to terminals quickly - if you then try to close the terminals vscode will hang and eventually timeout and need to be reopened.


Related