Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jeff-hykin/macro-commander
💾 📦 ✅ Automate everything in VS code
https://github.com/jeff-hykin/macro-commander
keybindings macros
Last synced: 2 days ago
JSON representation
💾 📦 ✅ Automate everything in VS code
- Host: GitHub
- URL: https://github.com/jeff-hykin/macro-commander
- Owner: jeff-hykin
- License: mit
- Created: 2019-05-16T20:42:55.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-09-09T16:35:21.000Z (2 months ago)
- Last Synced: 2024-11-06T20:44:19.552Z (5 days ago)
- Topics: keybindings, macros
- Language: Shell
- Homepage:
- Size: 1.69 MB
- Stars: 42
- Watchers: 3
- Forks: 8
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
## What does this do?
It lets you write a quick sequence of commands/scripts to automate VS Code tasks.## Example Usage (Code Examples are Futher Below)
- A run command that opens up a terminal, starts an SSH connection, and then runs commands on the SSH server
- 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.
- 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.
- A folder-specific start command, that pulls the latest changes, installs dependences, formats files, and then opens the debugger with a specific file.## How do I use it?
1. Find the name of commands you want to run. You can do this by going to the VS Code keybindings.json
(go to gear-icon -> keybindings, then press the ↪📄 in the top right corner)
All of the `"command":`'s (ex: `"command": "workbench.action.terminal.new"`) can be copied and pasted into the macro
2. Open up your VS Code settings.json and create a new section like this:
(go to gear-icon -> settings, then press the {}'s in the top right corner)
```jsonc
"macros": {
"exampleMacro1": [
// a simple command to open a new terminal
"workbench.action.terminal.new",
// OR a complex command (e.g. has arguments)
{ "command": "COMMAND_HERE", "args":/*stuff*/ }
// OR javascript
{ "javascript": [ "window.showInformationMessage('hello')" ] }
]
}
```
3. 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.
4. To create a keybinding to the macro, simply open the Keyboard Shortcuts (Gear-icon -> Keyboard Shortcuts) and start to type "macros". All of the macros have a "macros." prefix.## Command Options
Every element in the macro array should be one of these:
- string = simple command
- command with args is an object like this `{"command": "COMMAND_HERE", "args":/*stuff*/}`
- command + javascript using javascript-injections like this: `{ "injections": [], "command": "COMMAND_HERE", "args":/*stuff*/}`
- run javascript likes this: `{ "javascript": [] }`
- call a hidden console (childProcess) like this `{ "injections": [], "hiddenConsole": [] }`NOTE:
1. The javascript runs inside of an async function, meaning you can use the `await` keyword
2. Javascript has access to:
- the `vscode` object: (`vscode.commands`, `vscode.env`, `vscode.workspace`, `vscode.tasks`, etc.) is documented here: https://code.visualstudio.com/api/
- the `window` object, a shortcut to `vscode.window`.
- the `path` object (from node path)
- the `fs` object (from node fs)
- the `macroTools` object (helpers from this library)3. The `"withResultOf"` argument can be a single value (`3.14159 || null`) or multiple statements (`await window.showInputBox(); console.log("hi")`).
## Quality of Life Tools
If you're writing JS macros:
- Use the "Macros: JS to JSON" command to make it easier to write macros (select JS code, then call the command)
- Use the "Macros: JSON to JS" command to make it easier to read/edit macros## What are some examples?
See also [Level up your Coding with Macros](http://gedd.ski/post/level-up-coding-with-macros/)
```jsonc
"macros": {
"terminalExample1": [
// a simple command to open a new terminal
"workbench.action.terminal.new",
// a command with arguments, that sends text to the terminal
{
"command": "workbench.action.terminal.sendSequence",
// the "text" arg was decided by VS Code (not me)
"args": { "text": "echo hello\n" }
},
],
"terminalExample2" : [
// Run some javascript
{
"javascript": [
// docs for showInputBox: https://vscode-api.js.org/interfaces/vscode.InputBoxOptions.html
"sharedMacroInfo.personName = await window.showInputBox({ title: `Whats your name?` })",
]
},
// a simple command to open a new terminal
"workbench.action.terminal.new",
// combine javascript and command
{
"injections" : [
{ "replace": "$personName", "withResultOf": "macroTools.escapeShellArg(sharedMacroInfo.personName)" },
{ "replace": "$selectedText", "withResultOf": "macroTools.escapeShellArg(window.activeTextEditor.document.getText(window.activeTextEditor.selections[0]))" },
{ "replace": "$currentFile", "withResultOf": "macroTools.escapeShellArg(window.activeTextEditor.document.uri.fsPath)" },
{ "replace": "$currentFolder", "withResultOf": "macroTools.escapeShellArg(vscode.workspace.rootPath)" },
],
"command": "workbench.action.terminal.sendSequence",
"args": { "text": "echo hi $personName\necho 'you selected' $selectedText\necho 'the current file is: '$currentFile\necho the current folder is: $currentFolder\n" }
},
],
"userInputExample1" : [
// javascript execution (see https://code.visualstudio.com/api/extension-capabilities/common-capabilities)
{
// this has access to the `vscode` object, the `window` object and the `path` object (from node path)
// the javascript is also run inside of an async function, meaning you can use the `await` keyword
"javascript": "window.showInformationMessage(`You entered: ${await window.showInputBox()}`)"
},
],
"userInputExample2" : [
{
"javascript": [
"let response = await window.showInputBox()",
"let selectedText = window.activeTextEditor.document.getText(window.activeTextEditor.selections[0])",
"await window.showInformationMessage(`You entered: ${response}`)",
]
},
],
"fileInputExample1" : [
{
"javascript": [
"let cursorLineNumbers = window.activeTextEditor.selections.map(each=>each.start.line)",
"let cursorCharNumbers = window.activeTextEditor.selections.map(each=>each.start.character)",
"let textContent = window.activeTextEditor.document.lineAt(cursorLineNumbers[0]).text",
]
},
],
"userInputExample3" : [
{
"javascriptPath": "~/vs_code_stuff/macros/example3.js",
// if you open up that file^ it could contain:
// const { window } = require("vscode")
// const vscode = require("vscode")
// const path = require("path")
// const fs = require("fs")
//
// await window.showInformationMessage(`Howdy!`)
// NOTE: you cant use "import" or "export" because this script is evaled! not imported
},
],
"userInputExample4" : [
{
"javascriptPath": "./macros/build.js",
// this path will be relative to the current WORKSPACE
// if there is no active workspace, it will tell you to activate one (e.g. error)
},
],
"sharedInfo1" : [
{
// use this varible to share data between macros, and within the same macro
"javascript": "sharedMacroInfo.prevMessage = 'howdy'",
},
{
"javascript": "window.showInformationMessage(sharedMacroInfo.prevMessage)",
},
],
"javascriptPlusTerminalExample" : [
// run a hidden console command (runs in the background)
{
// NOTE: don't start a command in a hiddenConsole
// that doesn't finish! there's no good way
// of killing/canceling it
//
// this echo will never be seen
"hiddenConsole": [
"touch .gitignore",
"echo hello"
]
},
// combine javascript and hidden console commands
{
"injections" : [
{ "replace": "$currentFolder", "withResultOf": "vscode.workspace.rootPath" },
],
"hiddenConsole": [
"cd \"$currentFolder\"",
"touch .gitignore"
]
},
],
"terminalWithBashFunctions" : [
{
"injections" : [
{ "replace": "$currentFolder", "withResultOf": "vscode.workspace.rootPath" }
],
// I wanted to use aliases defined in my bash profile
// here's a hacky way of doing that
"hiddenConsole": [
"bash <=0) { let sel = sels[i];",
" if (sel.isEmpty) {r = editor.document.getWordRangeAtPosition(sel.start); sels[i] = new vscode.Selection(r.start, r.end);}",
" i--; }",
" editor.selections = sels;",
"}",
"function doTransform(i) { let sels = editor.selections;",
" if (i < 0 || i >= sels.length) { return; }",
" let sel = sels[i];",
" let word_matches = editor.document.getText(sel).matchAll(/([a-z]+|[A-Z][a-z]*|[^A-Za-z]+)/g);",
" let words = [];",
" for (const match of word_matches) {words.push(match[0].toLowerCase())};",
" editor.edit(eb => {eb.replace(sel, words.join('_'))}).then(x => { doTransform(i+1); });",
"}"
]
}
],
"transformToCamel": [
// Same as transformToSnake, but vice versa
{
"javascript": [
"let editor = window.activeTextEditor;",
"expandWords();",
"doTransform(0);",
"function expandWords() { let sels = editor.selections; let i = sels.length-1;",
" while (i >=0) { let sel = sels[i];",
" if (sel.isEmpty) {r = editor.document.getWordRangeAtPosition(sel.start); sels[i] = new vscode.Selection(r.start, r.end);}",
" i--; }",
" editor.selections = sels;",
"}",
"function doTransform(i) { let sels = editor.selections;",
" if (i < 0 || i >= sels.length) { return; }",
" let sel = sels[i];",
" let words = editor.document.getText(sel).split('_')",
" let camel_words = words.map(function(w) {return w[0].toUpperCase() + w.slice(1,).toLowerCase()});",
" editor.edit(eb => {eb.replace(sel, camel_words.join(''))}).then(x => { doTransform(i+1); });",
"}"
]
}
],
"replaceAllExample1": [
{
"javascript": [
//save the current clipboard
"var oldClip = await vscode.env.clipboard.readText(); ",//copies the selected text to the clipboard
"await vscode.commands.executeCommand('editor.action.clipboardCopyAction'); ",
"var testoSelezionato = await vscode.env.clipboard.readText(); ",//replace all "gatta" to "##########"
"var nuovoTesto = testoSelezionato.replace(/gatta/g, '##########'); ",//paste the new text
"if( nuovoTesto != testoSelezionato ) { ",
" await vscode.env.clipboard.writeText(nuovoTesto); ",
" await vscode.commands.executeCommand('editor.action.clipboardPasteAction'); ",
"} ",//restore the original clipboard
"await vscode.env.clipboard.writeText(oldClip); ",
],
},
],
"replaceAllExample2": [
{
"javascript": [
// parameters
"var cerca = 'gatta'; ",
"var opzioni = 'g'; ", // g = global; i = ignorecase
"var sostituisci = '##########'; ",
// get the currently active editor
"const myEditor = vscode.window.activeTextEditor; ",
"if (myEditor) {",
// perform an edit on the document associated with this text editor
"myEditor.edit(myEditBuilder => { ",// loop for each multi-cursor
"myEditor.selections.forEach(rangeSelezione => {",
// get selected text from a single multi-cursor
"var testoSelezionato = myEditor.document.getText(rangeSelezione);",
// replace all
"var nuovoTesto = testoSelezionato.replace(new RegExp(cerca, opzioni), sostituisci);",
// if the text has changed, I write it in the document
"if(nuovoTesto != testoSelezionato) { ",
"myEditBuilder.replace(rangeSelezione, nuovoTesto);",
"}",
"});",
"});",
"}",
]
}
],
}
```## Some Common Macro Steps
| Desired Action | Macro Step (example)
| -------------------------- | ------------------------------------------------------------------------------------------------
| execute a snippet | {"command": "type", "args": {"text": "mySnippetPrefixHere"}}, "insertSnippet" ]
| open a new terminal | "workbench.action.terminal.new"
| type into the terminal | { "command": "workbench.action.terminal.sendSequence", "args": { "text": "echo hello\n" } }
| message box | { "javascript": "window.showInformationMessage(`You entered: ${await window.showInputBox()}`)" }
| user input | { "javascript": [ "let response = await window.showInputBox()" ], ...
| user output | ... [ "await window.showInformationMessage(`You entered: ${response}`)" ] }
| Copy to clipboard | "editor.action.clipboardCopyAction"## Some Common Injections:
| Desired Value | JavaScript Expression
| ----------------------------- | ------------------------------------------------------------------------------------------------
| $userInput | await window.showInputBox()
| $currentFolder | vscode.workspace.rootPath
| $currentFile | window.activeTextEditor.document.uri.fsPath
| $currentFileName | path.basename(window.activeTextEditor.document.uri.fsPath)
| $currentFileNameNoExtension | path.basename(window.activeTextEditor.document.uri.fsPath).replace(/\\.[^/.]+$/, '')
| $currentFileDir | path.dirname(window.activeTextEditor.document.uri.fsPath)
| $selectionText | window.activeTextEditor.document.getText(window.activeTextEditor.selection)
| $clipboardText | vscode.env.clipboard.readText()
| $preferedLanguage | vscode.env.language ("en-US")
| $machineId | vscode.env.machineId (The name of computer you are running on)
| $sessionId | vscode.env.sessionId (A unique string that changes when VS Code restarts)
| $shellName | vscode.env.shell (The name of the default terminal shell)## Some Useful JavaScript (assuming: const doc = window.activeTextEditor.document)
| Category | Desired Action | JavaScript Code
| ------------- | ---------------------------------- | --------------------------------------------------------------
| Document text | The entire text | doc.getText()
| Document text | Part of the text | doc.getText(range)
| Document text | Range of the word at cursor | doc.getWordRangeAtPosition(position)
| Document text | The line of text at cursor | doc.lineAt(line: number)
| Document text | The line of text at a position | doc.lineAt(position) // a Position is a line number/char number pair
| Document text | Convert position to overall offset | doc.offsetAt(position)
| Document text | Convert overall offset to position | doc.positionAt(offset: number)
| Document info | EOL style | doc.eol // enum (LF=1, CRLF=2)
| Document info | File name | (doc.isUntitled ? "" : doc.fileName)
| Document info | Needs saving | doc.isDirty
| Document info | Line count | doc.lineCount
| Document info | Save to disk | doc.save()
| Multi-Select | If multi-selected... | if (window.activeTextEditor.selections.length > 1) { ... };
| Multi-Select | Only act if index is in range | if (window.activeTextEditor.selections[index]) { ... };
| Multi-Select | Scroll to a particular selection | window.activeTextEditor.revealRange(window.activeTextEditor.selections[index]);
| Multi-Select | Select only the last selection | window.activeTextEditor.selection = window.activeTextEditor.selections.pop();## Who made this?
[polyglot-jones](https://github.com/polyglot-jones) improved the documentation and added three big examples (unMultiSelectLast, transformToSnake, transformToCamel)
The old extension was made by [geddski](http://gedd.ski)
I modified it to have
- in-order execution
- javascript / javascript-injections
- async await
- keybindings + named execution
- error message pop-ups
- auto reload when settings was edited
- and a few other things