turbo-editors/turbo-golopublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-golo.git
git clone ssh://git@rickub.com/turbo-editors/turbo-golo.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo Golo d710c1b · on v1.0.2 · k33g · 12h ago
golo-tools.md · 119 lines · 14.4 KBmarkdown
Blame HistoryOpen raw

Golo tools — explanation

What is this about?

A Golo menu whose commands come from a TOML file, each run where the tool asked — a popup, a terminal window, or an editing window — and the open files re-read afterwards. This page is about why each of those is the way it is.

Why the output has three places to go, and a popup by default

The first version of this mechanism, in Turbo Go, put every command in a terminal window, and it was the wrong default for most of them.

A terminal is the right answer when the program is interactive or long: golo main.golo on a script that reads the keyboard with readln has to be answerable, and a script that serves HTTP with httpServe has to be interruptible with Ctrl-C. Neither is true of golo --test, which prints its report and ends. Giving that a whole window — one you then have to close, on a desktop where windows overlap and are numbered — is more ceremony than the result deserves.

A popup is the right answer for a command you run, read and dismiss. It is modal, which is a real cost and is named in the how-to: a gogolo build you did not expect to be slow — it runs the Go compiler — holds the editor until it finishes or you press Escape. That cost was accepted on purpose, because the alternative — a dialog appearing unbidden three seconds later — swallows whatever was being typed at the moment it arrives.

So the popup opens immediately and fills in. You see progress, nothing surprises you, and Escape both closes it and stops the command, which is the only way to interrupt something whose output is not in a terminal.

An editing window is the right answer for output you are going to work through: a long test report, or the Go source gogolo transpile prints. It is an ordinary buffer, so Ctrl-F searches it and Save as keeps it. It is filled once the command has ended rather than as it goes, because a buffer growing under the cursor while you search it is the opposite of what that mode is for.

None of those three is right for everything, which is why output is in the file rather than in the code. Run is the worked example: it is the first command in the starter file, it says terminal, and the comment beside it says why. So do Debug — the step debugger reads the keyboard — and REPL, which is nothing but a keyboard.

Why Run comes first

In Turbo MoonBit's starter file the first tool is moon check, because for a compiled language "is this sound?" is the question asked most often and the one that produces nothing. Golo is a scripting language, and the question asked most often is "what does it print?". So the first line of the Golo menu runs the file, and the second runs the tests.

Why a terminal window is still there

The editor already had one — a real pseudo-terminal with a VT emulator, built for the F8 windows — so output = "terminal" costs one field on its options and buys colours, paging, Ctrl-C, keyboard input and scrollback for nothing, because they are the same mechanisms every other terminal uses. GoloScript's own output uses those colours: golo --test draws green ticks, and the uiPrint family of builtins draws whatever the script asked for.

The window stays after the command exits, which is the point: the output is what you asked for, and a window that vanished with it would be useless.

Why the exit code is always in the title

A script that ends with nothing printed, or golo --test over a directory with no test files, prints nothing at all. A popup with an empty body and a neutral title is indistinguishable from one whose command has not started, and the reader is left guessing at the one thing they wanted to know.

So the title carries the verdict — — ok or — exit 1 — and an empty body says (no output) once the command has ended. While it is still running the body stays blank, because "(no output)" is a verdict and a running command has not reached one.

Why the commands are in a file

Eight commands hardwired into the editor would have answered the request. They would also have been wrong within a week.

Every command in the starter file goes through one of GoloScript's three binaries — golo, gogolo or wagolo — so none of them needs anything set up beyond the toolchain itself. That is a defensible default and it is nobody's universal answer. A project with one entry point wants golo main.golo without being asked which script. One that ships as a native binary wants gogolo build -o bin/app app.golo with the output fixed. One that targets the browser wants wagolo build -target=js and never wasi. One that runs under Docker wants docker run … k33g/gololang. None of that is knowable from here, and all of it is one line in a file.

So the eight are defaults, not code: they are the contents of the starter file that Golo ▸ Create tools file writes, and changing one is editing a file rather than rebuilding an editor. The file is read every time the menu opens, for the same reason the Snippets menu is: an edit should take effect at once, and the file is often open in the window behind the menu.

Commands go to sh -ccmd.exe /S /C on Windows — rather than being split into an argv here. The file is the user's own, so pipes, globs and && are features rather than hazards, and one entry can be golo --test && gogolo build -o app main.golo. Splitting an argv would mean inventing quoting rules for a string somebody wrote by hand.

Why there is no user-level tools file

Snippets are read from two files — yours and the project's — because your snippets are your habits and should follow you between projects.

Tools are not like that. They belong to a project's own toolchain: a global tools file would offer golo --test in a repository that has never heard of Golo, and a project that only ever interprets its scripts would get wagolo build in its menu with a TinyGo it never installed. The file is per-project, and that is the whole of the rule.

Why a tool may name its own menu

A menu called Golo holding docker compose up is a lie about what the menu is. The first tools file anybody writes outgrows Golo, because a project's commands are not all about the language it is written in: containers, databases, deploys, a Makefile target somebody added in 2019.

Two shapes were considered. A fixed second menu called Tools — everything Golo in Golo, everything else in Tools — is one key in the format and no naming problem at all, but it only moves the lie: a Tools menu holding docker compose up, psql, and a deploy script is just as undifferentiated, and the moment there are ten entries nobody can find one. And a second file, menus.toml, keeps the tools file simple at the cost of two files that have to agree about which tools exist.

So the menu is a free-form name on the tool, in the one file: menu = "Docker". A name nothing else uses creates the menu; leaving the key out means Golo. There is no list of allowed names, because a list would be a list of somebody else's projects.

Golo itself stays fixed on the bar rather than becoming just another name from the file. Golo ▸ Create tools file has to be reachable in a project that has no tools file at all — which is exactly the project that needs it — and a menu that only exists once the file exists cannot offer to write the file.

Why the hot key is not the file's to choose

The author of a tools file cannot know which letters are free. They can see File, Edit, Search, Run, Code, Options, Window, Snippets, Golo and Help on the bar, but only by counting the underlines, and a project shared between people would then depend on nobody adding a menu that collides.

Collisions here are silent, which is what makes them worth designing against. The bar answers the first menu whose hot key matches; a second menu claiming the same letter is not an error and draws normally — it simply never opens. That trap has already been sprung once in this family: Snippets and Search both wanted S, Snippets was the unreachable one, and every test passed. The fix then was to move Snippets to N by hand. Letting a file name menus makes that a permanent hazard rather than a one-off mistake, so the assignment is done by the editor: the first letter of the name nothing else claims.

Tildes written into the name are honoured when the letter is free, and quietly overridden when it is not. Refusing the file instead was the alternative, and it is worse: the clash depends on which menus exist, so a tools file that worked would break the day an editor release added a menu. Between a menu on a letter you did not ask for and a menu you cannot open, the first is the smaller loss.

When every letter of a name is taken, the menu gets no hot key at all. F10, the arrow keys and the mouse still reach it, and the alternative — reaching for a letter that is not in the name — would put an underline under nothing.

Why the bar is rebuilt from a stat

Menu.OnOpen refills a menu's items just before it drops down, which is how the Golo and Snippets menus follow their files without a restart. It cannot help here: the set of menus is part of the bar, not part of any one menu, and adding menu = "Docker" to the file should put Docker on the bar.

Reading and parsing the file on every turn of the event loop would do it, and would also be work done for nothing on every keystroke of a file nobody has edited. So the bar carries the size and modification time of the tools file it was built from, and one stat per turn decides whether to rebuild. Editing the file in the window in front of you, saving it, and watching the bar change is the case this is for.

Why open files are re-read, and only some of them

Golo has no formatter, so no starter command rewrites the file in front of you — but golo new writes a file into the directory, gogolo build -keep-go leaves a .go beside the script, and your own tools may do anything at all. Without anything further, the editor would sit on a stale copy of a file another command changed, and the next F2 would write your copy back over the command's work.

So when a command finishes, the editor re-reads every open file. The interesting part is which ones it refuses to touch.

A file with unsaved changes is left alone, and the status bar says how many were skipped. Reloading it would throw away work the user has not saved, which no amount of convenience justifies. And the conflict is genuine: the command and the unsaved edit disagree about what the file should say, and the editor is not in a position to decide. Naming it and stopping is the honest outcome.

Two smaller decisions inside that:

  • The cursor stays where it was, clamped into whatever the file now holds.
  • The undo history is discarded. Undoing back past a reload would restore text the file no longer has, which is worse than not being able to undo at all.

Why the reload happens on the event loop

The command's exit is noticed on the goroutine reading the terminal, which may not touch a buffer or the desktop. So it sets a flag, and the reload runs at the top of the next turn of the event loop.

This is the fourth thing in the library built that way — the language-server announcement, the terminal redraws, the autosave deadline, and now this. The rule they share is worth stating once more: the wake-up may be lost, so the state must not be. PostEvent drops what does not fit in its queue, so anything that depends on a message arriving is a bug waiting for a busy moment. A flag the loop checks for itself cannot go missing.

Why a command can ask for a value, and why it asks in double braces

golo needs a script. golo new needs a module name and a file name. gogolo build needs a script and an output path. wagolo build needs a target too. None of those can live in the tools file as a fixed string, because the answer is different every time — and a tool that cannot ask is a tool that has to be edited before each use, which is not a tool.

So a {{label}} in a command is a value the editor asks for first, in a box titled after the tool. Six of the eight Golo commands use it, which is deliberate: a feature demonstrated in the file everybody gets is a feature people find, and one described only in a comment is not. That is more placeholders than any sibling's starter file carries, and the reason is Golo's: with no manifest there is no moon run that knows what to run, so every command that touches a file has to be told which one.

Single braces were the obvious spelling and are wrong. awk '{print $1}' and find . -exec rm {} + are ordinary things to put in a tools file, and reading the first as a placeholder turns a working command into a box asking for "print $1". Double braces collide with almost nothing.

The value is quoted by default, because the alternative fails silently. A path with a space in it, substituted raw, becomes two arguments and the command reports something about a file that does not exist. Quoting makes that case work and makes the other case — "put these three flags on the end" — impossible, so ... inside the braces asks for the value verbatim. Two behaviours, both documented, rather than one that is wrong half the time.

Nothing is remembered on disk. The box starts from what was typed last time, for the session. Writing it into the project's own directory was considered and rejected: that directory holds what the project decided, and the script somebody ran while chasing one bug is not that.

A file that cannot be parsed is refused when it is read, not when the tool is chosen. An unclosed {{ reaching the shell is a command failing with braces in it, which names neither the tool nor the file; refusing at load names both. That is the same rule an unknown output value already follows.

The dialog is refused when it will not fit. A tool asking for more values than the terminal has rows would give a box whose OK button is below the bottom of the screen — answerable only by Escape, which cancels. Saying "this asks for twelve values and nine fit" is worse than nothing only if you would rather find out by trying.

How it relates to the rest

  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
# Golo tools — explanation

## What is this about?

A **Golo** menu whose commands come from a TOML file, each run where the tool asked — a popup, a terminal window, or an editing window — and the open files re-read afterwards. This page is about why each of those is the way it is.

## Why the output has three places to go, and a popup by default

The first version of this mechanism, in Turbo Go, put every command in a terminal window, and it was the wrong default for most of them.

A terminal is the right answer when the program is *interactive or long*: `golo main.golo` on a script that reads the keyboard with `readln` has to be answerable, and a script that serves HTTP with `httpServe` has to be interruptible with `Ctrl-C`. Neither is true of `golo --test`, which prints its report and ends. Giving that a whole window — one you then have to close, on a desktop where windows overlap and are numbered — is more ceremony than the result deserves.

A popup is the right answer for a command you run, read and dismiss. It is modal, which is a real cost and is named in the [how-to](../how-to/run-golo-commands.md): a `gogolo build` you did not expect to be slow — it runs the Go compiler — holds the editor until it finishes or you press Escape. That cost was accepted on purpose, because the alternative — a dialog appearing unbidden three seconds later — swallows whatever was being typed at the moment it arrives.

So the popup **opens immediately and fills in**. You see progress, nothing surprises you, and Escape both closes it and stops the command, which is the only way to interrupt something whose output is not in a terminal.

An editing window is the right answer for output you are going to work through: a long test report, or the Go source `gogolo transpile` prints. It is an ordinary buffer, so `Ctrl-F` searches it and `Save as` keeps it. It is filled once the command has ended rather than as it goes, because a buffer growing under the cursor while you search it is the opposite of what that mode is for.

None of those three is right for everything, which is why `output` is in the file rather than in the code. `Run` is the worked example: it is the first command in the starter file, it says `terminal`, and the comment beside it says why. So do `Debug` — the step debugger reads the keyboard — and `REPL`, which is nothing but a keyboard.

## Why Run comes first

In Turbo MoonBit's starter file the first tool is `moon check`, because for a compiled language "is this sound?" is the question asked most often and the one that produces nothing. Golo is a scripting language, and the question asked most often is "what does it print?". So the first line of the Golo menu runs the file, and the second runs the tests.

## Why a terminal window is still there

The editor already had one — a real pseudo-terminal with a VT emulator, built for the `F8` windows — so `output = "terminal"` costs one field on its options and buys colours, paging, `Ctrl-C`, keyboard input and scrollback for nothing, because they are the same mechanisms every other terminal uses. GoloScript's own output uses those colours: `golo --test` draws green ticks, and the `uiPrint` family of builtins draws whatever the script asked for.

The window stays after the command exits, which is the point: the output is what you asked for, and a window that vanished with it would be useless.

## Why the exit code is always in the title

A script that ends with nothing printed, or `golo --test` over a directory with no test files, prints nothing at all. A popup with an empty body and a neutral title is indistinguishable from one whose command has not started, and the reader is left guessing at the one thing they wanted to know.

So the title carries the verdict — `— ok` or `— exit 1` — and an empty body says `(no output)` once the command has ended. While it is still running the body stays blank, because "(no output)" is a verdict and a running command has not reached one.

## Why the commands are in a file

Eight commands hardwired into the editor would have answered the request. They would also have been wrong within a week.

Every command in the starter file goes through one of GoloScript's three binaries — `golo`, `gogolo` or `wagolo` — so none of them needs anything set up beyond the toolchain itself. That is a defensible default and it is nobody's universal answer. A project with one entry point wants `golo main.golo` without being asked which script. One that ships as a native binary wants `gogolo build -o bin/app app.golo` with the output fixed. One that targets the browser wants `wagolo build -target=js` and never `wasi`. One that runs under Docker wants `docker run … k33g/gololang`. None of that is knowable from here, and all of it is one line in a file.

So the eight are **defaults, not code**: they are the contents of the starter file that **Golo ▸ Create tools file** writes, and changing one is editing a file rather than rebuilding an editor. The file is read every time the menu opens, for the same reason the Snippets menu is: an edit should take effect at once, and the file is often open in the window behind the menu.

Commands go to `sh -c``cmd.exe /S /C` on Windows — rather than being split into an argv here. The file is the user's own, so pipes, globs and `&&` are features rather than hazards, and one entry can be `golo --test && gogolo build -o app main.golo`. Splitting an argv would mean inventing quoting rules for a string somebody wrote by hand.

## Why there is no user-level tools file

Snippets are read from two files — yours and the project's — because your snippets are your habits and should follow you between projects.

Tools are not like that. They belong to a project's own toolchain: a global tools file would offer `golo --test` in a repository that has never heard of Golo, and a project that only ever interprets its scripts would get `wagolo build` in its menu with a TinyGo it never installed. The file is per-project, and that is the whole of the rule.

## Why a tool may name its own menu

A menu called **Golo** holding `docker compose up` is a lie about what the menu is. The first tools file anybody writes outgrows Golo, because a project's commands are not all about the language it is written in: containers, databases, deploys, a `Makefile` target somebody added in 2019.

Two shapes were considered. A **fixed second menu** called Tools — everything Golo in Golo, everything else in Tools — is one key in the format and no naming problem at all, but it only moves the lie: a Tools menu holding `docker compose up`, `psql`, and a deploy script is just as undifferentiated, and the moment there are ten entries nobody can find one. And a **second file**, `menus.toml`, keeps the tools file simple at the cost of two files that have to agree about which tools exist.

So the menu is a **free-form name on the tool**, in the one file: `menu = "Docker"`. A name nothing else uses creates the menu; leaving the key out means Golo. There is no list of allowed names, because a list would be a list of somebody else's projects.

Golo itself stays fixed on the bar rather than becoming just another name from the file. **Golo ▸ Create tools file** has to be reachable in a project that has no tools file at all — which is exactly the project that needs it — and a menu that only exists once the file exists cannot offer to write the file.

## Why the hot key is not the file's to choose

The author of a tools file cannot know which letters are free. They can see `File`, `Edit`, `Search`, `Run`, `Code`, `Options`, `Window`, `Snippets`, `Golo` and `Help` on the bar, but only by counting the underlines, and a project shared between people would then depend on nobody adding a menu that collides.

Collisions here are **silent**, which is what makes them worth designing against. The bar answers the first menu whose hot key matches; a second menu claiming the same letter is not an error and draws normally — it simply never opens. That trap has already been sprung once in this family: `Snippets` and `Search` both wanted `S`, `Snippets` was the unreachable one, and every test passed. The fix then was to move Snippets to `N` by hand. Letting a file name menus makes that a permanent hazard rather than a one-off mistake, so the assignment is done by the editor: the first letter of the name nothing else claims.

Tildes written into the name are honoured **when the letter is free**, and quietly overridden when it is not. Refusing the file instead was the alternative, and it is worse: the clash depends on which menus exist, so a tools file that worked would break the day an editor release added a menu. Between a menu on a letter you did not ask for and a menu you cannot open, the first is the smaller loss.

When every letter of a name is taken, the menu gets no hot key at all. `F10`, the arrow keys and the mouse still reach it, and the alternative — reaching for a letter that is not in the name — would put an underline under nothing.

## Why the bar is rebuilt from a stat

`Menu.OnOpen` refills a menu's items just before it drops down, which is how the Golo and Snippets menus follow their files without a restart. It cannot help here: the *set* of menus is part of the bar, not part of any one menu, and adding `menu = "Docker"` to the file should put Docker on the bar.

Reading and parsing the file on every turn of the event loop would do it, and would also be work done for nothing on every keystroke of a file nobody has edited. So the bar carries the size and modification time of the tools file it was built from, and one `stat` per turn decides whether to rebuild. Editing the file in the window in front of you, saving it, and watching the bar change is the case this is for.

## Why open files are re-read, and only some of them

Golo has no formatter, so no starter command rewrites the file in front of you — but `golo new` writes a file into the directory, `gogolo build -keep-go` leaves a `.go` beside the script, and your own tools may do anything at all. Without anything further, the editor would sit on a stale copy of a file another command changed, and the next `F2` would write your copy back over the command's work.

So when a command finishes, the editor re-reads every open file. The interesting part is which ones it refuses to touch.

**A file with unsaved changes is left alone**, and the status bar says how many were skipped. Reloading it would throw away work the user has not saved, which no amount of convenience justifies. And the conflict is genuine: the command and the unsaved edit disagree about what the file should say, and the editor is not in a position to decide. Naming it and stopping is the honest outcome.

Two smaller decisions inside that:

- **The cursor stays where it was**, clamped into whatever the file now holds.
- **The undo history is discarded.** Undoing back past a reload would restore text the file no longer has, which is worse than not being able to undo at all.

## Why the reload happens on the event loop

The command's exit is noticed on the goroutine reading the terminal, which may not touch a buffer or the desktop. So it sets a flag, and the reload runs at the top of the next turn of the event loop.

This is the fourth thing in the library built that way — the language-server announcement, the terminal redraws, the autosave deadline, and now this. The rule they share is worth stating once more: **the wake-up may be lost, so the state must not be.** `PostEvent` drops what does not fit in its queue, so anything that depends on a message arriving is a bug waiting for a busy moment. A flag the loop checks for itself cannot go missing.

## Why a command can ask for a value, and why it asks in double braces

`golo` needs a script. `golo new` needs a module name and a file name. `gogolo build` needs a script and an output path. `wagolo build` needs a target too. None of those can live in the tools file as a fixed string, because the answer is different every time — and a tool that cannot ask is a tool that has to be edited before each use, which is not a tool.

So a `{{label}}` in a command is a value the editor asks for first, in a box titled after the tool. **Six of the eight Golo commands use it**, which is deliberate: a feature demonstrated in the file everybody gets is a feature people find, and one described only in a comment is not. That is more placeholders than any sibling's starter file carries, and the reason is Golo's: with no manifest there is no `moon run` that knows what to run, so every command that touches a file has to be told which one.

**Single braces were the obvious spelling and are wrong.** `awk '{print $1}'` and `find . -exec rm {} +` are ordinary things to put in a tools file, and reading the first as a placeholder turns a working command into a box asking for "print $1". Double braces collide with almost nothing.

**The value is quoted by default**, because the alternative fails silently. A path with a space in it, substituted raw, becomes two arguments and the command reports something about a file that does not exist. Quoting makes that case work and makes the other case — "put these three flags on the end" — impossible, so `...` inside the braces asks for the value verbatim. Two behaviours, both documented, rather than one that is wrong half the time.

**Nothing is remembered on disk.** The box starts from what was typed last time, for the session. Writing it into the project's own directory was considered and rejected: that directory holds what the project decided, and the script somebody ran while chasing one bug is not that.

**A file that cannot be parsed is refused when it is read**, not when the tool is chosen. An unclosed `{{` reaching the shell is a command failing with braces in it, which names neither the tool nor the file; refusing at load names both. That is the same rule an unknown `output` value already follows.

**The dialog is refused when it will not fit.** A tool asking for more values than the terminal has rows would give a box whose OK button is below the bottom of the screen — answerable only by Escape, which cancels. Saying "this asks for twelve values and nine fit" is worse than nothing only if you would rather find out by trying.

## How it relates to the rest

- Every key of the file and every rule: [Golo tools reference](../reference/golo-tools.md)
- Using it: [How to run Golo commands from the editor](../how-to/run-golo-commands.md)
- The windows `output = "terminal"` uses, and why they are real terminals: [Terminal windows](terminal-windows.md)
- The other menu built from a file: [Snippets](snippets.md)