{"id":13596755,"url":"https://github.com/jeff-hykin/macro-commander","last_synced_at":"2025-06-28T12:35:56.041Z","repository":{"id":36474853,"uuid":"187098607","full_name":"jeff-hykin/macro-commander","owner":"jeff-hykin","description":"💾 📦 ✅ Automate everything in VS code","archived":false,"fork":false,"pushed_at":"2024-09-09T16:35:21.000Z","size":1769,"stargazers_count":53,"open_issues_count":6,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-27T02:11:55.186Z","etag":null,"topics":["keybindings","macros"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeff-hykin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-16T20:42:55.000Z","updated_at":"2025-06-20T10:45:07.000Z","dependencies_parsed_at":"2024-09-09T17:18:15.031Z","dependency_job_id":"e10342d7-4509-4e90-b8fa-774da248e3d8","html_url":"https://github.com/jeff-hykin/macro-commander","commit_stats":{"total_commits":329,"total_committers":2,"mean_commits":164.5,"dds":0.006079027355623046,"last_synced_commit":"10fac9f58d04c9cc5b8907b47912d2ba781cf1d2"},"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jeff-hykin/macro-commander","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeff-hykin%2Fmacro-commander","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeff-hykin%2Fmacro-commander/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeff-hykin%2Fmacro-commander/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeff-hykin%2Fmacro-commander/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeff-hykin","download_url":"https://codeload.github.com/jeff-hykin/macro-commander/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeff-hykin%2Fmacro-commander/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262432279,"owners_count":23310216,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["keybindings","macros"],"created_at":"2024-08-01T16:02:45.855Z","updated_at":"2025-06-28T12:35:56.025Z","avatar_url":"https://github.com/jeff-hykin.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"## What does this do?\nIt lets you write a quick sequence of commands/scripts to automate VS Code tasks.\n\n## Example Usage (Code Examples are Futher Below)\n- A run command that opens up a terminal, starts an SSH connection, and then runs commands on the SSH server\n- A new-project command that goes to where ever you typically save projects, uses the GUI to ask for the name of the project, creates the folder, creates a .gitignore with your preferences, runs an init command, and then opens the folder in your current VS Code window.\n- A command that opens a project folder, pulls the latest changes, opens several files in that folder, and then displays the recent changes in those files.\n- A folder-specific start command, that pulls the latest changes, installs dependences, formats files, and then opens the debugger with a specific file.\n\n## How do I use it?\n1. Find the name of commands you want to run. You can do this by going to the VS Code keybindings.json\n(go to gear-icon -\u003e keybindings, then press the ↪📄 in the top right corner)\nAll of the `\"command\":`'s (ex: `\"command\": \"workbench.action.terminal.new\"`) can be copied and pasted into the macro\n2. Open up your VS Code settings.json and create a new section like this:\n(go to gear-icon -\u003e settings, then press the {}'s in the top right corner)\n```jsonc\n\"macros\": {\n    \"exampleMacro1\": [\n        // a simple command to open a new terminal\n        \"workbench.action.terminal.new\",\n        \n        // OR a complex command (e.g. has arguments)\n        { \"command\": \"COMMAND_HERE\", \"args\":/*stuff*/ }\n        \n        // OR javascript\n        { \"javascript\": [ \"window.showInformationMessage('hello')\" ] }\n    ]\n}\n```\n3. To run the macro open the command pallet (cmd+shift+P or ctrl+shift+P) and type `run macro` then pick which one you want to run.\n4. To create a keybinding to the macro, simply open the Keyboard Shortcuts (Gear-icon -\u003e Keyboard Shortcuts) and start to type \"macros\". All of the macros have a \"macros.\" prefix.\n\n\n## Command Options\n\nEvery element in the macro array should be one of these:\n\n- string = simple command\n- command with args is an object like this `{\"command\": \"COMMAND_HERE\", \"args\":/*stuff*/}` \n- command + javascript using javascript-injections like this:  `{ \"injections\": [], \"command\": \"COMMAND_HERE\", \"args\":/*stuff*/}` \n- run javascript likes this: `{ \"javascript\": [] }` \n- call a hidden console (childProcess) like this `{ \"injections\": [], \"hiddenConsole\": [] }` \n\nNOTE:\n1. The javascript runs inside of an async function, meaning you can use the `await` keyword\n2. Javascript has access to:\n  - the `vscode` object: (`vscode.commands`, `vscode.env`, `vscode.workspace`, `vscode.tasks`, etc.) is documented here: https://code.visualstudio.com/api/\n  - the `window` object, a shortcut to `vscode.window`.\n  - the `path` object (from node path)\n  - the `fs` object (from node fs)\n  - the `macroTools` object (helpers from this library)\n\n3. The `\"withResultOf\"` argument can be a single value (`3.14159 || null`) or multiple statements (`await window.showInputBox(); console.log(\"hi\")`).\n\n## Quality of Life Tools\n\nIf you're writing JS macros:\n- Use the \"Macros: JS to JSON\" command to make it easier to write macros (select JS code, then call the command)\n- Use the \"Macros: JSON to JS\" command to make it easier to read/edit macros\n\n## What are some examples?\nSee also [Level up your Coding with Macros](http://gedd.ski/post/level-up-coding-with-macros/) \n```jsonc\n\"macros\": {\n    \"terminalExample1\": [\n        // a simple command to open a new terminal\n        \"workbench.action.terminal.new\",\n        // a command with arguments, that sends text to the terminal\n        {\n            \"command\": \"workbench.action.terminal.sendSequence\", \n            // the \"text\" arg was decided by VS Code (not me)\n            \"args\": { \"text\": \"echo hello\\n\" }\n        },\n    ],\n    \"terminalExample2\" : [\n        // Run some javascript\n        {\n            \"javascript\": [\n                // docs for showInputBox: https://vscode-api.js.org/interfaces/vscode.InputBoxOptions.html\n                \"sharedMacroInfo.personName = await window.showInputBox({ title: `Whats your name?` })\",\n            ]\n        },\n        // a simple command to open a new terminal\n        \"workbench.action.terminal.new\",\n        // combine javascript and command\n        {\n            \"injections\" : [\n                { \"replace\": \"$personName\", \"withResultOf\": \"macroTools.escapeShellArg(sharedMacroInfo.personName)\" },\n                { \"replace\": \"$selectedText\", \"withResultOf\": \"macroTools.escapeShellArg(window.activeTextEditor.document.getText(window.activeTextEditor.selections[0]))\" },\n                { \"replace\": \"$currentFile\", \"withResultOf\": \"macroTools.escapeShellArg(window.activeTextEditor.document.uri.fsPath)\" },\n                { \"replace\": \"$currentFolder\", \"withResultOf\": \"macroTools.escapeShellArg(vscode.workspace.rootPath)\" },\n            ],\n            \"command\": \"workbench.action.terminal.sendSequence\",\n            \"args\": { \"text\": \"echo hi $personName\\necho 'you selected' $selectedText\\necho 'the current file is: '$currentFile\\necho the current folder is: $currentFolder\\n\" }\n        },\n    ],\n    \"userInputExample1\" : [\n        // javascript execution (see https://code.visualstudio.com/api/extension-capabilities/common-capabilities)\n        {\n            // this has access to the `vscode` object, the `window` object and the `path` object (from node path)\n            // the javascript is also run inside of an async function, meaning you can use the `await` keyword\n            \"javascript\": \"window.showInformationMessage(`You entered: ${await window.showInputBox()}`)\"\n        },\n    ],\n    \"userInputExample2\" : [\n        {\n            \"javascript\": [\n                \"let response = await window.showInputBox()\",\n                \"let selectedText = window.activeTextEditor.document.getText(window.activeTextEditor.selections[0])\",\n                \"await window.showInformationMessage(`You entered: ${response}`)\",\n            ]\n        },\n    ],\n    \"fileInputExample1\" : [\n        {\n            \"javascript\": [\n                \"let cursorLineNumbers = window.activeTextEditor.selections.map(each=\u003eeach.start.line)\",\n                \"let cursorCharNumbers = window.activeTextEditor.selections.map(each=\u003eeach.start.character)\",\n                \"let textContent = window.activeTextEditor.document.lineAt(cursorLineNumbers[0]).text\",\n            ]\n        },\n    ],\n    \"userInputExample3\" : [\n        {\n            \"javascriptPath\": \"~/vs_code_stuff/macros/example3.js\",\n            // if you open up that file^  it could contain:\n            //     const { window } = require(\"vscode\")\n            //     const vscode = require(\"vscode\")\n            //     const path = require(\"path\")\n            //     const fs = require(\"fs\")\n            // \n            //     await window.showInformationMessage(`Howdy!`)\n            \n            // NOTE: you cant use \"import\" or \"export\" because this script is evaled! not imported\n        },\n    ],\n    \"userInputExample4\" : [\n        {\n            \"javascriptPath\": \"./macros/build.js\",\n            // this path will be relative to the current WORKSPACE\n            // if there is no active workspace, it will tell you to activate one (e.g. error)\n        },\n    ],\n    \"sharedInfo1\" : [\n        {\n            // use this varible to share data between macros, and within the same macro\n            \"javascript\": \"sharedMacroInfo.prevMessage = 'howdy'\",\n        },\n        {\n            \"javascript\": \"window.showInformationMessage(sharedMacroInfo.prevMessage)\",\n        },\n    ],\n    \"javascriptPlusTerminalExample\" : [\n        // run a hidden console command (runs in the background)\n        {\n            // NOTE: don't start a command in a hiddenConsole\n            //       that doesn't finish! there's no good way \n            //       of killing/canceling it\n            // \n            // this echo will never be seen\n            \"hiddenConsole\": [\n                \"touch .gitignore\",\n                \"echo hello\"\n            ]\n        },\n        // combine javascript and hidden console commands\n        {\n            \"injections\" : [\n                { \"replace\": \"$currentFolder\", \"withResultOf\": \"vscode.workspace.rootPath\" },\n            ],\n            \"hiddenConsole\": [\n               \"cd \\\"$currentFolder\\\"\", \n               \"touch .gitignore\"\n            ]\n        },\n    ],\n    \"terminalWithBashFunctions\" : [\n        {\n            \"injections\" : [\n                { \"replace\": \"$currentFolder\", \"withResultOf\": \"vscode.workspace.rootPath\" }\n            ],\n            // I wanted to use aliases defined in my bash profile\n            // here's a hacky way of doing that\n            \"hiddenConsole\": [\n                \"bash \u003c\u003cHEREDOC\",\n                \"    source ~/.bash_profile\",\n                \"    cd \\\"$currentFolder\\\"\",\n                \"    echo now I can use aliases\",\n                \"HEREDOC\",\n            ]\n        }\n    ],\n    \"exampleOfCommonInjections\" : [\n        {\n            \"injections\" : [\n                { \"replace\": \"$userInput\",       \"withResultOf\": \"await window.showInputBox()\" },\n                { \"replace\": \"$currentFolder\",   \"withResultOf\": \"vscode.workspace.rootPath\" },\n                { \"replace\": \"$currentFile\",     \"withResultOf\": \"window.activeTextEditor.document.uri.fsPath\" },\n                { \"replace\": \"$currentFileName\", \"withResultOf\": \"path.basename(window.activeTextEditor.document.uri.fsPath)\" },\n                { \"replace\": \"$currentFileNameNoExtension\", \"withResultOf\": \"path.basename(window.activeTextEditor.document.uri.fsPath).replace(/\\\\.[^/.]+$/, '')\" },\n                { \"replace\": \"$currentFileDir\", \"withResultOf\": \"path.dirname(window.activeTextEditor.document.uri.fsPath)\" },\n            ],\n            \"hiddenConsole\" : \"echo $userInput\"\n        }\n    ],\n    \"WriteToOutputExample\": [\n        {\n            \"javascript\": [\n                //create a new OUTPUT log, with name \"MyLog\"\n                \"const myOutput = window.createOutputChannel('MyLog'); \",   \n    \n                //write a message to OUTPUT log                              \n                \"await myOutput.appendLine('Lorem ipsum dolor sit amet, consectetur adipisci elit.'); \",  \n\n                //show the OUTPUT log       \n                \"await myOutput.show(); \",                                                                   \n            ],\n        },\n    ],\n    \"unMultiSelectLast\": [\n        // For when you Ctrl-Click to multiselect 10 times and on the eleventh get it wrong. \n        // Just press Ctrl-0 (or whatever key you assign) to unselect the eleventh, then carry on.\n        // (See also, https://github.com/danseethaler/vscode-tab-through-selections, for more along this line.)\n        {\n            \"javascript\": [\n                \"const editor = window.activeTextEditor;\",\n                \"const newSelections = editor.selections.slice(0, editor.selections.length - 1);\",\n                \"editor.selections = newSelections;\"\n            ]\n        }\n    ],\n    \"transformToSnake\": [\n        // A multi-select friendly macro to convert from CamelCase to snake_case.\n        // If any particular selection is empty (just a cursor), this will automatically expand it to the whole word first.\n        // (Kudos to https://stackoverflow.com/users/398630/brainslugs83 for some pointers)\n        {\n            \"javascript\": [\n                \"let editor = window.activeTextEditor;\",\n                \"expandWords();\",\n                \"doTransform(0);\",\n                \"function expandWords() { let sels = editor.selections; let i = sels.length-1;\",\n                \"  while (i \u003e=0) { let sel = sels[i];\",\n                \"    if (sel.isEmpty) {r = editor.document.getWordRangeAtPosition(sel.start); sels[i] = new vscode.Selection(r.start, r.end);}\",\n                \"    i--; }\",\n                \"  editor.selections = sels;\",\n                \"}\",\n                \"function doTransform(i) { let sels = editor.selections;\",\n                \"  if (i \u003c 0 || i \u003e= sels.length) { return; }\",\n                \"  let sel = sels[i];\",\n                \"  let word_matches = editor.document.getText(sel).matchAll(/([a-z]+|[A-Z][a-z]*|[^A-Za-z]+)/g);\",\n                \"  let words = [];\",\n                \"  for (const match of word_matches) {words.push(match[0].toLowerCase())};\",\n                \"  editor.edit(eb =\u003e {eb.replace(sel, words.join('_'))}).then(x =\u003e { doTransform(i+1); });\",\n                \"}\"\n            ]\n        }\n    ],\n    \"transformToCamel\": [\n        // Same as transformToSnake, but vice versa\n        {\n            \"javascript\": [\n                \"let editor = window.activeTextEditor;\",\n                \"expandWords();\",\n                \"doTransform(0);\",\n                \"function expandWords() { let sels = editor.selections; let i = sels.length-1;\",\n                \"  while (i \u003e=0) { let sel = sels[i];\",\n                \"    if (sel.isEmpty) {r = editor.document.getWordRangeAtPosition(sel.start); sels[i] = new vscode.Selection(r.start, r.end);}\",\n                \"    i--; }\",\n                \"  editor.selections = sels;\",\n                \"}\",\n                \"function doTransform(i) { let sels = editor.selections;\",\n                \"  if (i \u003c 0 || i \u003e= sels.length) { return; }\",\n                \"  let sel = sels[i];\",\n                \"  let words = editor.document.getText(sel).split('_')\",\n                \"  let camel_words = words.map(function(w) {return w[0].toUpperCase() + w.slice(1,).toLowerCase()});\",\n                \"  editor.edit(eb =\u003e {eb.replace(sel, camel_words.join(''))}).then(x =\u003e { doTransform(i+1); });\",\n                \"}\"\n            ]\n        }\n    ],\n    \"replaceAllExample1\": [\n        {\n            \"javascript\": [\n                //save the current clipboard\n                \"var oldClip = await vscode.env.clipboard.readText(); \",    \n\n                //copies the selected text to the clipboard\n                \"await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); \",\n                \"var testoSelezionato = await vscode.env.clipboard.readText(); \",   \n\n                //replace all \"gatta\" to \"##########\"\n                \"var nuovoTesto = testoSelezionato.replace(/gatta/g, '##########'); \",   \n\n                //paste the new text\n                \"if( nuovoTesto != testoSelezionato ) { \",          \n                \"   await vscode.env.clipboard.writeText(nuovoTesto); \", \n                \"   await vscode.commands.executeCommand('editor.action.clipboardPasteAction'); \",\n                \"} \",          \n\n                //restore the original clipboard\n                \"await vscode.env.clipboard.writeText(oldClip); \", \n            ],\n        },\n    ],\n    \"replaceAllExample2\": [\n        { \n            \"javascript\": [\n                // parameters\n                \"var cerca       = 'gatta'; \",\n                \"var opzioni     = 'g'; \",             // g = global; i = ignorecase     \n                \"var sostituisci = '##########'; \",\n                // get the currently active editor\n                \"const myEditor = vscode.window.activeTextEditor; \",\n                \"if (myEditor) {\",\n                    // perform an edit on the document associated with this text editor\n                    \"myEditor.edit(myEditBuilder =\u003e { \",\n\n                        // loop for each multi-cursor\n                        \"myEditor.selections.forEach(rangeSelezione =\u003e {\",\n                            // get selected text from a single multi-cursor\n                            \"var testoSelezionato = myEditor.document.getText(rangeSelezione);\",\n                            // replace all \n                            \"var nuovoTesto = testoSelezionato.replace(new RegExp(cerca, opzioni), sostituisci);\",\n                            // if the text has changed, I write it in the document\n                            \"if(nuovoTesto != testoSelezionato) { \",\n                                \"myEditBuilder.replace(rangeSelezione, nuovoTesto);\",\n                            \"}\",\n                        \"});\",\n                    \"});\",\n                \"}\",\n            ]\n        }\n    ],    \n}\n```\n\n## Some Common Macro Steps\n| Desired Action             | Macro Step (example)\n| -------------------------- | ------------------------------------------------------------------------------------------------\n| execute a snippet          | {\"command\": \"type\", \"args\": {\"text\": \"mySnippetPrefixHere\"}}, \"insertSnippet\" ]\n| open a new terminal        | \"workbench.action.terminal.new\"\n| type into the terminal     | { \"command\": \"workbench.action.terminal.sendSequence\", \"args\": { \"text\": \"echo hello\\n\" } }\n| message box                | { \"javascript\": \"window.showInformationMessage(`You entered: ${await window.showInputBox()}`)\" }\n| user input                 | { \"javascript\": [ \"let response = await window.showInputBox()\" ], ...\n| user output                | ... [ \"await window.showInformationMessage(`You entered: ${response}`)\" ] }\n| Copy to clipboard          | \"editor.action.clipboardCopyAction\"\n\n\n## Some Common Injections:\n| Desired Value                 | JavaScript Expression\n| ----------------------------- | ------------------------------------------------------------------------------------------------\n| $userInput                    | await window.showInputBox()\n| $currentFolder                | vscode.workspace.rootPath\n| $currentFile                  | window.activeTextEditor.document.uri.fsPath\n| $currentFileName              | path.basename(window.activeTextEditor.document.uri.fsPath)\n| $currentFileNameNoExtension   | path.basename(window.activeTextEditor.document.uri.fsPath).replace(/\\\\.[^/.]+$/, '')\n| $currentFileDir               | path.dirname(window.activeTextEditor.document.uri.fsPath)\n| $selectionText                | window.activeTextEditor.document.getText(window.activeTextEditor.selection)\n| $clipboardText                | vscode.env.clipboard.readText()\n| $preferedLanguage             | vscode.env.language (\"en-US\")\n| $machineId                    | vscode.env.machineId (The name of computer you are running on)\n| $sessionId                    | vscode.env.sessionId (A unique string that changes when VS Code restarts)\n| $shellName                    | vscode.env.shell (The name of the default terminal shell)\n\n\n## Some Useful JavaScript (assuming: const doc = window.activeTextEditor.document)\n| Category      | Desired Action                     | JavaScript Code\n| ------------- | ---------------------------------- | --------------------------------------------------------------\n| Document text | The entire text                    | doc.getText()\n| Document text | Part of the text                   | doc.getText(range)\n| Document text | Range of the word at cursor        | doc.getWordRangeAtPosition(position)\n| Document text | The line of text at cursor         | doc.lineAt(line: number)\n| Document text | The line of text at a position     | doc.lineAt(position)   // a Position is a line number/char number pair\n| Document text | Convert position to overall offset | doc.offsetAt(position)\n| Document text | Convert overall offset to position | doc.positionAt(offset: number)\n| Document info | EOL style                          | doc.eol                // enum (LF=1, CRLF=2)\n| Document info | File name                          | (doc.isUntitled ? \"\" : doc.fileName)\n| Document info | Needs saving                       | doc.isDirty\n| Document info | Line count                         | doc.lineCount\n| Document info | Save to disk                       | doc.save()\n| Multi-Select  | If multi-selected...               | if (window.activeTextEditor.selections.length \u003e 1) { ... };\n| Multi-Select  | Only act if index is in range      | if (window.activeTextEditor.selections[index]) { ... };\n| Multi-Select  | Scroll to a particular selection   | window.activeTextEditor.revealRange(window.activeTextEditor.selections[index]);\n| Multi-Select  | Select only the last selection     | window.activeTextEditor.selection = window.activeTextEditor.selections.pop();\n\n\n## Who made this?\n\u003cbr\u003e[polyglot-jones](https://github.com/polyglot-jones) improved the documentation and added three big examples (unMultiSelectLast, transformToSnake, transformToCamel)\n\u003cbr\u003eThe old extension was made by [geddski](http://gedd.ski)\n\u003cbr\u003eI modified it to have\n- in-order execution\n- javascript / javascript-injections\n- async await\n- keybindings + named execution \n- error message pop-ups\n- auto reload when settings was edited\n- and a few other things\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeff-hykin%2Fmacro-commander","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeff-hykin%2Fmacro-commander","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeff-hykin%2Fmacro-commander/lists"}