Reference: JavaScript tools
Neutral description of
.turbo-js/tools.toml, the JavaScript menu, and what running a command does.
File
| Property | Value |
|---|---|
| Path | ./.turbo-js/tools.toml |
| Search | The working directory only. Parent directories are not searched. |
| Read | Every time one of its menus opens, for the items |
| Re-read | Whenever the file's size or modification time changes, for the set of menus |
| Missing file | Not an error |
| Unreadable file | An error, shown in the menu |
| User-level file | None. Unlike snippets, there is no ~/.config/turbo-js/tools.toml. |
File format
One [[tool]] table per command.
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | yes | What the menu shows. May carry a hot key written with tildes, as in "~T~est". |
command |
string | yes | The shell command to run |
output |
string | no | Where its output goes: popup, terminal or editor. Absent means popup. |
menu |
string | no | Which menu it appears in. Absent means JavaScript. Any name; the menu is created for you. May carry a hot key written with tildes. |
menu is not checked against a list, because there is no list: a name that no other tool uses simply creates a menu. A tool with no name, no command, or an output naming something that does not exist makes the whole file an error. An unknown output is refused rather than corrected: "termnial" would otherwise look as though it had worked while sending the output somewhere else.
Example
[[tool]]
name = "~T~est"
command = "node --test"
output = "popup"
[[tool]]
name = "~E~cho"
command = "echo TADA"
output = "terminal"
menu = "Tools"
The starter file
JavaScript ▸ Create tools file writes these seven, in this order:
| Name | Command | Output | Menu |
|---|---|---|---|
~I~nstall |
npm install |
popup |
JavaScript |
~F~ormat |
npx prettier --write . |
popup |
JavaScript |
~L~int |
npx eslint . |
popup |
JavaScript |
~T~est |
node --test |
popup |
JavaScript |
~R~un |
node {{script, e.g. main.js}} |
terminal |
JavaScript |
~S~tart |
npm start |
terminal |
JavaScript |
~E~cho |
echo 🎉 tada! |
terminal |
Tools |
Install comes first because it is the first thing run in a fresh clone, and the one that has to have run before any of the others can. One of them asks for a value before it runs — Run, because a Node project has no single entry point the editor could know — and one names a menu of its own. Those two features are invisible unless the starter file shows them.
Run and Start get a terminal: a script may read the keyboard, and npm start is usually a server that has to be interrupted with Ctrl-C. Format and Lint go through npx, which runs the project's own Prettier and ESLint when package.json depends on them and downloads them into a cache otherwise; ESLint 9 needs an eslint.config.js in the project. Test is Node's own runner, which needs nothing installed: it runs every *.test.js, *.spec.js and test/**/*.js file under the current directory.
There is no Build, because JavaScript has no compile step. node --check {{script}} is the syntax check somebody who wants one adds.
Every tool names its output, including the ones that name the default: the key is the interesting part of the format, and a file where it appears once is a file where nobody notices it exists.
The item is greyed out once the project has a tools file, so it cannot overwrite one. The file is written through a temporary file in the same directory, renamed into place.
The JavaScript menu
Always on the bar, whether or not a tools file exists. Its hot key is Alt-J.
| Item | Condition |
|---|---|
One line per tool with no menu, in file order |
The file holds at least one |
Cannot read tools, greyed out |
The file is present but unreadable |
Create tools file |
The project has no tools file |
Open tools file |
The project has one |
Menus a tool asks for
A menu naming anything other than JavaScript puts a menu of that name on the bar.
| Property | Value |
|---|---|
| Position | Between JavaScript and Help |
| Order | The order each name first appears in the file |
| Items | One line per tool naming that menu, in file order. Nothing else — Create tools file and Open tools file stay in JavaScript. |
| Unreadable file | No menus at all; the JavaScript menu carries the error |
| While the editor runs | Added, removed and renamed as the file changes, without restarting |
Hot keys
Assigned automatically, because a name from a file cannot be checked against the fixed menus in advance.
| Case | Result |
|---|---|
| No tildes in the name | The first letter no other menu has claimed is marked. Tools becomes ~T~ools: T is free. Format becomes For~m~at: F is File's, o is Options', r is Run's. |
| Tildes naming a free letter | Kept as written. Doc~k~er answers to Alt-K. |
| Tildes naming a taken letter | Dropped, and a free letter chosen instead. ~F~oo becomes F~o~o. |
| Every letter taken | No hot key. F10 and the mouse still open it. |
The letters the editor's own menus hold are F, E, S, R, C, O, W, N (Snippets), A (Agent), J (JavaScript) and H.
Running a command
Common to every output:
| Property | Value |
|---|---|
| Shell | /bin/sh -c "<command>" on Linux and macOS; cmd.exe /S /C "<command>" — the shell %COMSPEC% names — on Windows |
| Directory | The directory the editor was started in |
| Standard error | Merged into standard output, in the order the command wrote them |
Going through a shell means pipes, globs, && and ; all work, so one tool can be a sequence. On Windows the shell is cmd.exe, which knows &&, | and > but does not expand globs, and where ; is not a separator.
output = "popup"
| Property | Value |
|---|---|
| Opens | Immediately, before the command has finished |
| Modal | Yes: nothing else in the editor can be used while it is up |
| Fills in | As output arrives, following it until you scroll back |
| Title while running | <command> — running |
| Title when finished | <command> — ok, or <command> — exit <n> |
| Empty output, finished | Shows (no output) |
| Empty output, running | Shows nothing |
| Output cap | 10000 lines; past it the oldest go and a … n earlier lines dropped … line says so |
| Key | Effect |
|---|---|
↑ ↓ PgUp PgDn Home End |
Read through the output |
| Wheel | The same |
Escape, Enter, Close |
Close it, stopping the command if it is still running |
Closing stops the command because there is no other way to interrupt one whose output is not in a terminal.
output = "terminal"
| Property | Value |
|---|---|
| Window | A terminal window of its own, titled with the command |
| Environment | The editor's own, with TERM set to xterm-256color |
| After it exits | The window stays, showing its output |
| Modal | No: the editor carries on beside it |
Because it is a real terminal, colours, paging, Ctrl-C and reading from the keyboard all work — node --test's green ticks, a script reading process.stdin, a server logging until it is interrupted. See Terminal windows.
Keys in a finished terminal window:
| Key | Effect |
|---|---|
Shift-PgUp, Shift-PgDn |
Read back through the output |
Ctrl-W |
Close the window |
| Anything else | Reaches the editor, not the dead shell |
output = "editor"
| Property | Value |
|---|---|
| Shows | A popup while it runs, as above |
| On closing the popup | An editing window holding the output, titled with the command |
| Filled | Once, when the command has finished — not as it goes |
| The window | An ordinary editing window with no file name: searchable with Ctrl-F, and Save as keeps it |
Reloading after a command
When a command finishes, every open file is considered.
| The file | What happens |
|---|---|
| Unmodified, and changed on disk | Re-read; its syntax is re-decided and its title refreshed |
| Unmodified, and unchanged on disk | Left alone, not counted |
| Has unsaved changes | Left alone and counted as skipped |
| Has never been named | Left alone |
| Has gone from disk | Left alone |
The cursor stays where it was, clamped into whatever the file now holds. The undo history is discarded, because undoing back past a reload would restore text the file no longer has.
The project tree is refreshed at the same moment — which is how node_modules/ and package-lock.json, written by npm install, appear in it.
| Status bar | When |
|---|---|
Running <command> |
The window opens |
Reloaded 2 files |
Two files were re-read, none skipped |
Reloaded 2 files; 1 file with unsaved changes left alone |
Some were skipped |
Command finished; 1 file with unsaved changes left alone |
Nothing was re-read, something was skipped |
Errors
| Message | Cause |
|---|---|
Cannot read tools in the menu |
The file is present but not valid TOML, or holds a tool with no name or no command |
Already there: .turbo-js/tools.toml |
Creating in a project that already has one. Unreachable from the menu, which greys the item out; still possible for a caller that is not a menu. |
This project has no .turbo-js/tools.toml yet. |
Opening in a project that has none, likewise |
Cannot tell which directory this is: … |
The working directory could not be read |
Terminal windows are not supported on this platform yet |
Running a command in a terminal needs a pseudo-terminal, which Linux, macOS and Windows have; see Terminal windows |
Asking for a value
A {{label}} anywhere in a command is a value the editor asks for before it runs, in a box titled after the tool. The text between the braces is what the box asks for.
| Written | Asked for | Substituted |
|---|---|---|
{{script, e.g. main.js}} |
script, e.g. main.js |
shell-quoted |
{{arguments...}} |
arguments |
verbatim |
A value is shell-quoted by default, so a path with a space in it stays one argument. A trailing ... inside the braces asks for it verbatim instead, which is how one field can stand for several arguments.
[[tool]]
name = "Run with ~a~rguments"
command = "node main.js {{arguments...}}"
output = "terminal"
| Rule | Behaviour |
|---|---|
| Several placeholders | One box, one field each, in the order they appear in the command |
| The same label twice | One field; every occurrence gets what is typed into it |
| A label written both ways | Asked for once; each occurrence honours its own braces |
| Escape, or Cancel | The command does not run |
| A field left empty | Substituted as empty — the command reports its own complaint |
| Running the tool again | The box starts from what was typed last time, for this session only |
| More fields than fit on screen | Refused, with a message saying how many fit |
Double braces, not single. awk '{print $1}' and find . -exec rm {} + are ordinary commands, and a single-brace syntax would read the first as a request for a value called print $1.
Nothing is written to disk. A value somebody typed this afternoon is not a decision the project made, so it does not go in the project's own directory.
Errors
| Error | Cause |
|---|---|
tool "X": "{{script" is never closed |
An opening {{ with no }} after it |
tool "X": {{}} asks for a value but does not say what it is |
A placeholder with no label, or one that is only ... |
Both are refused when the file is read, so a half-typed placeholder never reaches the shell with its braces still in it.
See also
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
|