Stop the menu from opening on the previous evaluation's answers (#6601)
* Evaluate menu guards one run at a time A second evaluation starting while one was in flight could not replace it. Process ignores a command change until the next run and `running = true` is a no-op while running, so setting them did nothing -- but clearing `collected` first threw away the lines the running script had already emitted. Its tail then landed as the entire result, and every id missing from it went back to showing, since `when:` only hides a row on an explicit false. That is how Setup > Defaults > Browser ends up listing browsers that are not installed. Queue the evaluation instead and run it once the one in flight lands, the way provider enumeration already waits its turn. * Answer repeated menu guard questions once per evaluation The menu opens on the last evaluation's answers, so however long the guard batch takes is how long a row can contradict the state it describes: stop a recording and Screenrecord still offers to stop it, because the `pgrep` that would hide it is queued behind fifty package lookups. Almost none of that time is the questions, it is asking them one process at a time. The shipped menu runs `omarchy-pkg-present` 54 times and `omarchy-cmd-present` 23, and reads `omarchy-default-browser` once per row in Defaults > Browser. Prepend a prelude that answers all of it inside the one guard process, off a single package listing, bash's own PATH lookup, and one capture per reader command. The captures are eager because `checked:` reads them inside `$()`, where a lazy memo would not outlive the subshell. Takes the shipped batch from 1.49s to 0.25s with identical answers for all 175 guards. * Make the guard prelude answer exactly as the commands it stands in for The prelude only helps if it is indistinguishable from the commands it shadows, and it was not: - `pacman -Q` resolves a name through what installed packages provide, so with gvim installed it reports `vim` as present. A set built from `pacman -Qq` sees only names, so `install.editor.vim` came back and offered to install what was already there. Build the set from provides too, and send version constraints, which no set can answer, to pacman. - `omarchy-cmd-present` uses `command -v`, which finds builtins; `type -P` searches PATH alone and disagreed on every one of them. - Shadowing a reader with a function caught far more than the plain `$(reader)` the rows use: `command -v omarchy-dns` got the function name, and `VAR=x omarchy-channel-current` got an answer captured without the variable. Substitute the captured value into the expression instead and leave every other form to run the real command. - A reader that exits nonzero could take the batch down under a login shell with errexit set. Also keep the results of a batch that was killed rather than finished, since a row whose `when:` went unanswered shows, which is the failure this set of changes exists to remove. Costs 0.25s -> 0.33s against 1.49s before any of this, still with answers identical to evaluating each guard on its own. * Read every provide pacman reports, wrapped or not `pacman -Qi` wraps a long list onto indented continuation lines whenever COLUMNS is set in the environment, which the login shell the batch runs under may well have done. Reading only the line that starts with `Provides` dropped the rest: at COLUMNS=80 that is 537 of 856 provides on this machine, which puts back exactly the "offers to install what is already there" failure the provides lookup was added to prevent. Follow the continuation lines instead. The version-constraint case was also not testing what it claimed. Interpolating the argument into the shadow's script text let `bash>=1` parse as a redirection, so the shadow was handed `bash` and quietly agreed for the wrong reason -- and left an `=1` file behind, which got committed. Pass arguments as argv to both sides, drop the file, and wrap gvim's provides in the stub so the parser is held to the format pacman actually emits.
This commit is contained in:
@@ -947,16 +947,22 @@ Item {
|
||||
|
||||
property var whenResults: ({}) // id → true|false (allow visibility)
|
||||
property var checkedResults: ({}) // id → true|false (show ✓)
|
||||
property bool guardsPending: false
|
||||
|
||||
function evaluateGuards() {
|
||||
var script = ""
|
||||
var ids = Object.keys(root.items)
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var entry = root.items[ids[i]]
|
||||
if (!entry) continue
|
||||
if (entry.when) script += "if { " + entry.when + "; } >/dev/null 2>&1; then echo " + ids[i] + ":w:1; else echo " + ids[i] + ":w:0; fi\n"
|
||||
if (entry.checked) script += "if { " + entry.checked + "; } >/dev/null 2>&1; then echo " + ids[i] + ":c:1; else echo " + ids[i] + ":c:0; fi\n"
|
||||
// Process ignores a command change while it is running, and `collected`
|
||||
// belongs to the run in flight, so a second evaluation cannot overwrite
|
||||
// the first: it would throw away the lines already read and never start.
|
||||
// The surviving tail then lands as the whole answer, and every id lost
|
||||
// with it goes back to showing, since a `when:` only hides on an explicit
|
||||
// false. Wait for the run in flight and evaluate once it lands instead.
|
||||
if (guardProc.running) {
|
||||
root.guardsPending = true
|
||||
return
|
||||
}
|
||||
root.guardsPending = false
|
||||
|
||||
var script = MenuModel.guardScript(root.items)
|
||||
if (!script) {
|
||||
root.whenResults = ({})
|
||||
root.checkedResults = ({})
|
||||
@@ -973,7 +979,16 @@ Item {
|
||||
stdout: SplitParser {
|
||||
onRead: function(data) { guardProc.collected += data + "\n" }
|
||||
}
|
||||
onExited: {
|
||||
onExited: function(exitCode, exitStatus) {
|
||||
// A batch that was killed rather than finished has only told us about
|
||||
// the rows it reached, and a row whose `when:` went unanswered shows.
|
||||
// Keep the last complete set rather than let a half-read one through.
|
||||
// A signal leaves the exit code at 0, so the status is what tells us.
|
||||
if (exitCode !== 0 || exitStatus !== 0) {
|
||||
if (root.guardsPending) Qt.callLater(function() { root.evaluateGuards() })
|
||||
return
|
||||
}
|
||||
|
||||
var nextWhen = ({})
|
||||
var nextChecked = ({})
|
||||
var lines = guardProc.collected.split("\n")
|
||||
@@ -994,6 +1009,9 @@ Item {
|
||||
root.whenResults = nextWhen
|
||||
root.checkedResults = nextChecked
|
||||
if (root.opened) root.rebuildDisplay()
|
||||
// Run the evaluation that had to stand aside. Deferred by a turn so the
|
||||
// process is settled before its command is set again.
|
||||
if (root.guardsPending) Qt.callLater(function() { root.evaluateGuards() })
|
||||
}
|
||||
}
|
||||
PanelWindow {
|
||||
|
||||
Reference in New Issue
Block a user