Keep queued typing selections when reopening the menu

This commit is contained in:
David Heinemeier Hansson committed 2026-10-09 16:51:38 +02:00
1 parent fbc6daea8b
commit 99f32458d6
5 files changed
+26 -7

No files matched your search

+12 -2
View File
@@ -3,7 +3,7 @@
# omarchy:summary=Pick one or multiple options from a menu
# omarchy:group=menu
# omarchy:name=select
# omarchy:args=prompt [option...] [-- --multiple --selected <value> --on-change <command-json> --width <pixels> --maxheight <pixels>]
# omarchy:args=prompt [option...] [-- --multiple --selected <value> --on-change <command-json> --change-key <key> --width <pixels> --maxheight <pixels>]
# omarchy:examples=omarchy menu select Format jpg png|omarchy-menu-select Resolution 4k 1080p 720p -- --width 400
# An option may lead with an icon, as "<glyph><TAB><label>", and may trail a
@@ -33,6 +33,7 @@ menu_width=""
menu_maxheight=""
multiple=0
on_change="[]"
change_key=""
selected=()
while (( $# > 0 )); do
@@ -48,6 +49,14 @@ while (( $# > 0 )); do
fi
on_change="$1"
;;
--change-key)
shift
if (( $# == 0 )); then
echo "omarchy-menu-select: --change-key requires a value" >&2
exit 1
fi
change_key="$1"
;;
--multiple)
multiple=1
;;
@@ -110,13 +119,14 @@ payload=$(perl -MEncode=decode -MJSON::PP=encode_json,decode_json -e '
multiple => $ARGV[6] ? JSON::PP::true : JSON::PP::false,
selected => decode_json($ARGV[7]),
onChange => decode_json($ARGV[8]),
changeKey => $ARGV[9],
selectionFile => decode("UTF-8", $ARGV[2]),
doneFile => decode("UTF-8", $ARGV[3])
};
$payload->{width} = int($ARGV[4]) if length($ARGV[4] // "");
$payload->{maxHeight} = int($ARGV[5]) if length($ARGV[5] // "");
print encode_json($payload)
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight" "$multiple" "$selected_json" "$on_change")
' "$prompt" "$options_json" "$selection_file" "$done_file" "$menu_width" "$menu_maxheight" "$multiple" "$selected_json" "$on_change" "$change_key")
omarchy-shell shell summon omarchy.menu "$payload" >/dev/null || exit 2
+1 -1
View File
@@ -58,7 +58,7 @@ def choose(title, catalog, selected, kind, overrides=None):
rows = picker_rows(catalog, selected)
args = ["omarchy-menu-select", title] + list(rows.values())
on_change = ["python", str(setup.ROOT / "default/input-methods/typing.py"), "apply", kind, json.dumps({"catalog": catalog, "overrides": overrides or {}})]
args += ["--", "--multiple", "--width", "620", "--maxheight", "650", "--on-change", json.dumps(on_change)]
args += ["--", "--multiple", "--width", "620", "--maxheight", "650", "--on-change", json.dumps(on_change), "--change-key", "typing:" + kind]
for key in selected:
args += ["--selected", rows[key][1:]]
result = subprocess.run(args, text=True, capture_output=True)
+2
View File
@@ -170,3 +170,5 @@ how the pickers behind menu actions (`omarchy-menu-plugin`,
Pass `-- --multiple` to `omarchy-menu-select` for a multi-select request. Repeat `--selected <value>` to preselect returned values (labels plus any subtext, without icons). Clicking or pressing Enter toggles an option without closing the menu; Apply returns a JSON array in option order, including `[]` when all choices are deselected. Cancellation still exits 1. Selections survive filtering.
With `--on-change <JSON command array>`, each toggle runs that command with the proposed selection JSON appended as its final argument, and no Apply row is shown. The checkbox changes only after the command succeeds; a failed command retains the previous selection. Changes persist when the menu closes. The typing selectors use this mode.
For immediate multi-select requests, `--change-key <key>` identifies the setting being changed across picker openings. The menu keeps queued selections for that key when reopened, so a new toggle cannot discard a change still being saved. Without a key, the command array identifies the setting.
+10 -4
View File
@@ -59,6 +59,7 @@ Item {
property var dmenuOptions: []
property bool dmenuMultiple: false
property var dmenuSelected: []
property string dmenuChangeKey: ""
property var dmenuOnChange: []
property string selectionFile: ""
property string doneFile: ""
@@ -908,7 +909,10 @@ Item {
dmenuMultiple = mode === "select" && payload.multiple === true
dmenuOnChange = dmenuMultiple && Array.isArray(payload.onChange)
&& payload.onChange.every(function(arg) { return typeof arg === "string" }) ? payload.onChange : []
dmenuChangeKey = String(payload.changeKey || JSON.stringify(dmenuOnChange))
dmenuSelected = MenuModel.dmenuSelections(dmenuOptions, Array.isArray(payload.selected) ? payload.selected : [], dmenuOnChange.length > 0)
if (dmenuOnChange.length > 0)
dmenuSelected = MenuModel.dmenuSelections(dmenuOptions, selectionProc.requestedSelection(), true)
selectionFile = String(payload.selectionFile || "")
doneFile = String(payload.doneFile || "")
requestActive = !!doneFile
@@ -988,22 +992,23 @@ Item {
Process {
id: selectionProc
property int serial: 0
property string changeKey: ""
property var selected: []
property var queue: []
property string collected: ""
function requestedSelection() {
for (var i = queue.length - 1; i >= 0; i--) {
if (queue[i].serial === root.requestSerial) return queue[i].selected
if (queue[i].key === root.dmenuChangeKey) return queue[i].selected
}
if (running && serial === root.requestSerial) return selected
if (running && changeKey === root.dmenuChangeKey) return selected
return root.dmenuSelected
}
function enqueue(values) {
var selected = MenuModel.dmenuSelections(root.dmenuOptions, values, true)
queue = queue.concat([{
serial: root.requestSerial, selected: selected,
serial: root.requestSerial, key: root.dmenuChangeKey, selected: selected,
command: root.dmenuOnChange.concat([JSON.stringify(selected)])
}])
if (!running) startNext()
@@ -1014,6 +1019,7 @@ Item {
var change = queue[0]
queue = queue.slice(1)
serial = change.serial
changeKey = change.key
selected = change.selected
collected = ""
command = change.command
@@ -1025,7 +1031,7 @@ Item {
onStreamFinished: selectionProc.collected = text
}
onExited: function(exitCode) {
if (serial === root.requestSerial && root.dmenuActive) {
if (changeKey === root.dmenuChangeKey && root.dmenuActive && root.dmenuOnChange.length > 0) {
if (exitCode === 0) {
var applied = selected
if (collected.trim()) {
+1
View File
@@ -65,6 +65,7 @@ class TypingSetupTest(unittest.TestCase):
args = process.call_args.args[0]
self.assertEqual(args[2], "\tKorean")
self.assertIn("--multiple", args)
self.assertEqual(args[args.index("--change-key") + 1], "typing:input")
self.assertEqual(args[-2:], ["--selected", "Korean"])
def test_apply_rejects_unknown_values(self):