basedpl 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SAX2B.woff2 ADDED
Binary file
package/basedpl_wasm.js CHANGED
@@ -97,6 +97,17 @@ export function name_cpu(name) {
97
97
  wasm.name_cpu(ptr0, len0);
98
98
  }
99
99
 
100
+ /**
101
+ * The pauses before each retry of a timing that fails, in seconds: `rustymath::timing::RETRY_PAUSES`.
102
+ * @returns {Float64Array}
103
+ */
104
+ export function retry_pauses() {
105
+ const ret = wasm.retry_pauses();
106
+ var v1 = getArrayF64FromWasm0(ret[0], ret[1]).slice();
107
+ wasm.__wbindgen_free(ret[0], ret[1] * 8, 8);
108
+ return v1;
109
+ }
110
+
100
111
  /**
101
112
  * Every glyph's row, as JSON, for the language bar.
102
113
  * @returns {string}
Binary file
package/bpl-editor.js ADDED
@@ -0,0 +1,471 @@
1
+ // Generated by basedpl.editors.browser.
2
+ export const grammar = {"name": "bpl", "displayName": "BPL", "scopeName": "source.bpl", "fileTypes": ["bpl"], "patterns": [{"begin": "[⍝]", "beginCaptures": {"0": {"name": "comment.line.bpl"}}, "end": "$", "contentName": "comment.line.bpl", "patterns": [], "applyEndPatternLast": true}, {"begin": "\"", "beginCaptures": {"0": {"name": "string.quoted.double.bpl"}}, "end": "\"|$", "contentName": "string.quoted.double.bpl", "patterns": [{"match": "\"\"", "name": "string.quoted.double.bpl"}], "applyEndPatternLast": true, "endCaptures": {"0": {"name": "string.quoted.double.bpl"}}}, {"match": "'.?'", "name": "string.quoted.single.bpl"}, {"match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←⎕•⍺⍵⍶⍹∇⍢⍝⋄⁝\\?¯∞⍬\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿¨ᵇ⍨↣⍤∘⍠⍥↢\\.⊗⌸⍣⇄⊘@∂⍚⌺/⌿\\\\⍀⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+", "name": "variable.other.bpl"}, {"match": "[•][A-Za-z_∆⍙][A-Za-z0-9_∆⍙]*", "name": "support.function.bpl"}, {"match": "(?:[¯]?[∞]|[¯]?(\\d+([.]\\d+)?|[.]\\d+)([eEₑ][¯]?\\d+)?([jJⱼ][¯]?(\\d+([.]\\d+)?|[.]\\d+)([eEₑ][¯]?\\d+)?|[rᵣ][¯]?\\d+)?)(?:ₓ₁|ₓ₂|ₓ₄|ₓ₈|ₓ|ₕ|ₛ|ₚ)?|[ₑⱼᵣ]", "name": "constant.numeric.bpl"}, {"match": "[⍬](?:ₓ₁|ₓ₂|ₓ₄|ₓ₈|ₓ|ₕ|ₛ|ₚ)?", "name": "constant.numeric.bpl"}, {"match": "\\$[tfn]", "name": "constant.numeric.bpl"}, {"match": "\\$[ad]", "name": "string.quoted.double.bpl"}, {"begin": "[\\])](?=(?:ₓ₁|ₓ₂|ₓ₄|ₓ₈|ₓ|ₕ|ₛ|ₚ))", "beginCaptures": {"0": {"name": "source.bpl"}}, "end": "(?:ₓ₁|ₓ₂|ₓ₄|ₓ₈|ₓ|ₕ|ₛ|ₚ)|$", "contentName": "constant.numeric.bpl", "patterns": [], "applyEndPatternLast": true, "endCaptures": {"0": {"name": "constant.numeric.bpl"}}}, {"match": "[\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿]", "name": "support.function.bpl"}, {"match": "(?:ₓ₁|ₓ₂|ₓ₄|ₓ₈|ₓ|ₕ|ₛ|ₚ)", "name": "keyword.operator.monadic.bpl"}, {"match": "[¨ᵇ⍨⊗⌸⇄∂/⌿\\\\⍀⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ]", "name": "keyword.operator.monadic.bpl"}, {"match": "[↣⍤∘⍠⍥↢\\.⍣⊘@⍚⌺]", "name": "keyword.operator.dyadic.bpl"}, {"match": "[⍺⍵⍶⍹∇⍢]", "name": "variable.parameter.bpl"}, {"match": "[←⎕⋄⁝\\?₀₁₂₃₄₅₆₇₈₉₋]", "name": "keyword.control.bpl"}]};
3
+ export const configuration = {"comments": {"lineComment": "⍝"}, "brackets": [["(", ")"], ["[", "]"], ["{", "}"]], "autoClosingPairs": [{"open": "(", "close": ")", "notIn": ["string", "comment"]}, {"open": "[", "close": "]", "notIn": ["string", "comment"]}, {"open": "{", "close": "}", "notIn": ["string", "comment"]}, {"open": "\"", "close": "\"", "notIn": ["string", "comment"]}, {"open": "'", "close": "'", "notIn": ["string", "comment"]}], "surroundingPairs": [{"open": "(", "close": ")"}, {"open": "[", "close": "]"}, {"open": "{", "close": "}"}, {"open": "\"", "close": "\""}, {"open": "'", "close": "'"}]};
4
+ export const editorOptions = {"fontFamily": "SAX2B, monospace", "unicodeHighlight": {"ambiguousCharacters": false}};
5
+ const bar = // Based on Adám Brudzewsky's APL language bar: https://abrudz.github.io/lb
6
+ // MIT License, Copyright (c) 2011-2020 Nikolay G. Nikolov and Adam Brudzevski.
7
+ // BPL name completion, editor adapters, dark mode and overlay layout by Jeremy Howard.
8
+ ((symbols, input, layout) => {
9
+ function attach(monaco, container, magics = false) {
10
+ const d = container.ownerDocument, w = d.defaultView, events = new AbortController();
11
+ const on = (target, name, fn, capture = false) => target.addEventListener(name, fn, {capture, signal: events.signal});
12
+ const {inCode, bplStart, entry, press, reset} = input(symbols, layout);
13
+ const shortcuts = new Map(symbols.map(({glyph, shortcut}) => [glyph, shortcut]));
14
+ let leftAlt = false, rightAlt = false;
15
+
16
+ function textareaRect(t) {
17
+ const mirror = d.createElement('div'), caret = d.createElement('span'), css = getComputedStyle(t), rect = t.getBoundingClientRect();
18
+ for (const p of ['font', 'lineHeight', 'letterSpacing', 'padding', 'border', 'boxSizing', 'width', 'tabSize']) mirror.style[p] = css[p];
19
+ Object.assign(mirror.style, {position: 'fixed', visibility: 'hidden', whiteSpace: 'pre-wrap', overflowWrap: 'break-word',
20
+ left: `${rect.left - t.scrollLeft}px`, top: `${rect.top - t.scrollTop}px`});
21
+ mirror.textContent = t.value.slice(0, t.selectionStart);
22
+ caret.textContent = '\u200b';
23
+ mirror.append(caret);
24
+ d.body.append(mirror);
25
+ const result = caret.getBoundingClientRect();
26
+ mirror.remove();
27
+ return result;
28
+ }
29
+
30
+ function editor(target) {
31
+ if (target.closest?.('.text_cell, .jp-MarkdownCell')) return;
32
+ const m = monaco?.editor.getEditors().find(e => e.hasTextFocus());
33
+ if (m) {
34
+ const model = m.getModel(), p = m.getPosition(), selection = m.getSelection();
35
+ if (!model || !p || !selection) return;
36
+ const valid = () => m.getDomNode()?.isConnected && m.getModel() === model && !m.getOption(monaco.editor.EditorOption.readOnly)
37
+ && (model.getLanguageId() === 'bpl' || magics && bplStart({text: model.getValue(), pos: model.getOffsetAt(m.getPosition())}) >= 0);
38
+ if (!valid()) return;
39
+ const e = {id: model, valid, text: model.getValue(), pos: model.getOffsetAt(p), empty: selection.isEmpty(), bpl: model.getLanguageId() === 'bpl',
40
+ rect: () => {
41
+ const r = m.getDomNode().getBoundingClientRect(), c = m.getScrolledVisiblePosition(p);
42
+ return {left: r.left + c.left, bottom: r.top + c.top + c.height};
43
+ },
44
+ insert: (text, from) => {
45
+ if (!valid()) return;
46
+ const selection = m.getSelection();
47
+ from ??= model.getOffsetAt(selection.getStartPosition());
48
+ const a = model.getPositionAt(from), b = selection.getEndPosition();
49
+ m.pushUndoStop();
50
+ m.executeEdits('aplnb', [{range: {startLineNumber: a.lineNumber, startColumn: a.column,
51
+ endLineNumber: b.lineNumber, endColumn: b.column}, text}], () => {
52
+ const end = model.getPositionAt(from + text.length);
53
+ return [new monaco.Selection(end.lineNumber, end.column, end.lineNumber, end.column)];
54
+ });
55
+ m.pushUndoStop();
56
+ m.focus();
57
+ }};
58
+ return bplStart(e) >= 0 ? e : undefined;
59
+ }
60
+ if (!magics) return;
61
+ const view = target.closest?.('.cm-editor')?.querySelector('.cm-content')?.cmTile?.view;
62
+ if (view) {
63
+ if (view.state.readOnly) return;
64
+ const sel = view.state.selection.main;
65
+ const e = {id: view, valid: () => view.dom.isConnected && !view.state.readOnly
66
+ && bplStart({text: view.state.doc.toString(), pos: view.state.selection.main.head}) >= 0,
67
+ text: view.state.doc.toString(), pos: sel.head, empty: sel.empty,
68
+ rect: () => view.coordsAtPos(sel.head),
69
+ insert: (text, from) => {
70
+ if (!e.valid()) return;
71
+ const sel = view.state.selection.main;
72
+ from ??= sel.from;
73
+ view.dispatch({changes: {from, to: sel.to, insert: text}, selection: {anchor: from + text.length}, userEvent: 'input.complete'});
74
+ view.focus();
75
+ }};
76
+ return bplStart(e) >= 0 ? e : undefined;
77
+ }
78
+ if (target.tagName !== 'TEXTAREA' || target.readOnly || target.disabled) return;
79
+ const e = {id: target, valid: () => target.isConnected && !target.readOnly && !target.disabled
80
+ && bplStart({text: target.value, pos: target.selectionStart, bpl: target.hasAttribute('data-bpl')}) >= 0,
81
+ text: target.value, pos: target.selectionStart, empty: target.selectionStart === target.selectionEnd, bpl: target.hasAttribute('data-bpl'),
82
+ rect: () => textareaRect(target),
83
+ insert: (text, from = target.selectionStart) => {
84
+ if (!e.valid()) return;
85
+ target.focus();
86
+ target.selectionStart = from;
87
+ d.execCommand('insertText', false, text);
88
+ }};
89
+ return bplStart(e) >= 0 ? e : undefined;
90
+ }
91
+
92
+ const host = d.createElement('div');
93
+ host.innerHTML = `<div class="ngn_lb" aria-label="BPL symbols"></div>
94
+ <div class="bpl_choices" role="group" aria-label="BPL symbol completions" hidden></div>
95
+ <style>
96
+ .ngn_lb,.bpl_choices{background:var(--bpl-bar-background,#eee);color:var(--bpl-bar-color,#111);font:15px var(--bpl-font,SAX2B),monospace}
97
+ .ngn_lb{border-bottom:1px solid #999;padding:2px}
98
+ .ngn_lb button,.bpl_choices button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:2px 4px}
99
+ .ngn_lb button:hover,.bpl_choices button:hover{background:#777;color:white}
100
+ .bpl_choices{position:fixed;z-index:2147483647;max-height:240px;max-width:calc(100vw - 16px);overflow:auto;border:1px solid #888;border-radius:4px;box-shadow:0 3px 12px #0003;padding:4px}
101
+ .bpl_choices button{display:block;width:100%;text-align:left;white-space:nowrap}
102
+ .bpl_choices small{display:block;padding:4px}
103
+ .ngn_lb .bpl_dead::after{content:'▾';font-size:9px;margin-left:1px}
104
+ .bpl_choices.bpl_menu{max-width:min(320px,calc(100vw - 16px))}
105
+ .bpl_menu button{display:inline-block;width:auto;min-width:28px;text-align:center}
106
+ </style>`;
107
+ container.append(host);
108
+ const bar = host.querySelector('.ngn_lb'), tip = host.querySelector('.bpl_choices');
109
+ let active, lastEditor, choice, keyInput = false;
110
+ const names = new Map(symbols.map(({glyph, name, monad, dyad}) =>
111
+ [glyph, [name, monad, dyad].filter(Boolean).join(' ')]));
112
+ function stateGlyphs(state) {
113
+ const {terminator, keys} = layout.states[state];
114
+ return [terminator, ...Object.values(keys).flatMap(value => typeof value === 'string' ? [value] : stateGlyphs(value.state))];
115
+ }
116
+ const groups = new Map(Object.values(layout.option).filter(value => value.state).map(({state}) =>
117
+ [layout.states[state].terminator, [...new Set(stateGlyphs(state))]]));
118
+ const variants = new Set([...groups.values()].flatMap(glyphs => glyphs.slice(1)));
119
+ function button(glyph, name) {
120
+ const b = d.createElement('button');
121
+ b.type = 'button'; b.textContent = glyph; b.title = (name || glyph) + (shortcuts.get(glyph) || ''); b.dataset.glyph = glyph;
122
+ return b;
123
+ }
124
+ // Related glyphs stay together; arrows meet at the boundary between menus and plain buttons.
125
+ const order = [...new Set([
126
+ ...'^_¯¨○⎕≡⍭↑↓←↦↢↣⇄∨∧⍺⍵∆∇⋄∊⍳⊂⊃∩∪⊥⊤⊣⊢×÷⌊⌈≤≥≠√π', ...groups.keys(), ...names.keys()
127
+ ])];
128
+ for (const glyph of [...order.filter(g => groups.has(g)), ...order.filter(g => !groups.has(g))]) {
129
+ if (!groups.has(glyph) && (variants.has(glyph) || /^[\x20-\x7e]+$/.test(glyph))) continue;
130
+ const b = button(glyph, names.get(glyph));
131
+ if (groups.has(glyph)) {
132
+ b.textContent = {'^': '²', '_': '₂'}[glyph] || glyph;
133
+ b.classList.add('bpl_dead');
134
+ b.setAttribute('aria-expanded', 'false');
135
+ b.title += ' (variants)';
136
+ }
137
+ bar.append(b);
138
+ }
139
+ function cancel() {
140
+ choice?.anchor?.setAttribute('aria-expanded', 'false');
141
+ active = undefined; choice = undefined; tip.hidden = true;
142
+ }
143
+ function show(e, item, anchor) {
144
+ tip.classList.toggle('bpl_menu', !!anchor);
145
+ tip.setAttribute('aria-label', anchor ? 'BPL symbol variants' : 'BPL symbol completions');
146
+ anchor?.setAttribute('aria-expanded', 'true');
147
+ if (choice?.item.query !== item.query || choice?.anchor !== anchor) {
148
+ tip.replaceChildren();
149
+ for (const [glyph, name] of item.found) {
150
+ const b = button(glyph, name);
151
+ if (!anchor) b.textContent = `${glyph} ${b.title}`;
152
+ tip.append(b);
153
+ }
154
+ if (!item.found.length) {
155
+ const note = d.createElement('small'); note.textContent = 'Unknown symbol'; tip.append(note);
156
+ }
157
+ }
158
+ choice = {e, item, anchor};
159
+ tip.hidden = false;
160
+ const r = anchor ? anchor.getBoundingClientRect() : e.rect();
161
+ tip.style.left = `${Math.max(4, Math.min(r.left, w.innerWidth - tip.offsetWidth - 8))}px`;
162
+ tip.style.top = `${Math.max(4, Math.min(r.bottom + 4, w.innerHeight - tip.offsetHeight - 8))}px`;
163
+ }
164
+ function refresh(target) {
165
+ const e = editor(target), item = e && entry(e);
166
+ if (item && active?.id === e.id && active.start === item.start) show(e, item);
167
+ else cancel();
168
+ }
169
+ on(bar, 'mousedown', ev => {
170
+ ev.preventDefault();
171
+ const b = ev.target.closest('button');
172
+ const opened = choice?.anchor === b;
173
+ cancel();
174
+ if (!lastEditor?.valid()) lastEditor = undefined;
175
+ if (b?.dataset.glyph && lastEditor) {
176
+ const glyph = b.dataset.glyph, group = groups.get(glyph);
177
+ if (group) {
178
+ if (!opened) show(lastEditor, {query: glyph, found: group.map(g => [g, names.get(g)])}, b);
179
+ } else lastEditor.insert(glyph);
180
+ }
181
+ });
182
+ on(tip, 'mousedown', ev => {
183
+ ev.preventDefault();
184
+ const b = ev.target.closest('button');
185
+ if (b && choice?.e.valid()) choice.e.insert(b.dataset.glyph, choice.item.start);
186
+ cancel();
187
+ });
188
+ on(d, 'focusin', ev => { if (!host.contains(ev.target)) lastEditor = editor(ev.target); cancel(); });
189
+ on(d, 'pointerdown', ev => { if (!host.contains(ev.target)) cancel(); }, true);
190
+ on(d, 'pointerup', ev => { if (!host.contains(ev.target)) lastEditor = editor(ev.target); });
191
+ for (const event of ['paste', 'cut', 'compositionstart', 'focusout']) on(d, event, cancel, true);
192
+ on(d, 'input', ev => {
193
+ if (!keyInput || ev.inputType !== 'insertText' && ev.inputType !== 'deleteContentBackward') cancel();
194
+ keyInput = false;
195
+ w.requestAnimationFrame(() => { if (!events.signal.aborted) { lastEditor = editor(ev.target); refresh(ev.target); } });
196
+ });
197
+ on(w, 'blur', () => { leftAlt = rightAlt = false; lastEditor = undefined; cancel(); reset(); });
198
+ on(w, 'focusin', reset, true);
199
+ on(w, 'keyup', ev => {
200
+ if (ev.code === 'AltLeft') leftAlt = false;
201
+ if (ev.code === 'AltRight') rightAlt = false;
202
+ keyInput = false; lastEditor = editor(ev.target); if (active) refresh(ev.target);
203
+ }, true);
204
+ on(w, 'keydown', ev => {
205
+ if (ev.key === 'Escape' && choice?.anchor) {
206
+ cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return;
207
+ }
208
+ keyInput = false;
209
+ if (ev.code === 'AltLeft') leftAlt = true;
210
+ if (ev.code === 'AltRight') rightAlt = true;
211
+ if (['Shift', 'Control', 'Alt', 'Meta'].includes(ev.key)) return;
212
+ const e = editor(ev.target);
213
+ if (!e || ev.isComposing || ev.defaultPrevented) { lastEditor = undefined; cancel(); reset(); return; }
214
+ if (lastEditor && lastEditor.id !== e.id) { cancel(); reset(); }
215
+ lastEditor = e;
216
+ const body = bplStart(e);
217
+ const pressed = press(ev, leftAlt && !rightAlt && !ev.getModifierState('AltGraph') && body >= 0);
218
+ if (pressed) {
219
+ if (pressed.text) e.insert(pressed.text);
220
+ if (pressed.stop) { cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return; }
221
+ }
222
+ const item = entry(e), plain = !ev.ctrlKey && !ev.altKey && !ev.metaKey;
223
+ const tab = ev.key === 'Tab' && plain && !ev.shiftKey, enter = ev.key === 'Enter';
224
+ const typed = active?.id === e.id && active.start === item?.start;
225
+ const delimiter = plain && ev.key.length === 1 && !/[a-z]/i.test(ev.key);
226
+ if (item && (tab || typed && (enter || delimiter))) {
227
+ if (item.found.length === 1) {
228
+ e.insert(item.found[0][0], item.start);
229
+ cancel();
230
+ } else if (tab) {
231
+ active = {id: e.id, start: item.start};
232
+ show(e, item);
233
+ }
234
+ if (tab) { ev.preventDefault(); ev.stopImmediatePropagation(); return; }
235
+ }
236
+ if (ev.key === '`' && plain) {
237
+ const updated = editor(ev.target), body = updated ? bplStart(updated) : -1;
238
+ if (updated && body >= 0 && updated.empty && inCode(updated.text.slice(body, updated.pos))) active = {id: updated.id, start: updated.pos};
239
+ else cancel();
240
+ } else if (!(typed && plain && (/^[a-z]$/i.test(ev.key) || ev.key === 'Backspace'))) cancel();
241
+ keyInput = plain && (ev.key.length === 1 || ev.key === 'Backspace');
242
+ }, true);
243
+ return {dispose() { events.abort(); reset(); lastEditor = undefined; cancel(); host.remove(); }};
244
+ }
245
+
246
+ function page(monaco) {
247
+ const d = document;
248
+ if (d.querySelector('.bpl_page_bar') || d.querySelector('meta[name=generator][content^=quarto]') && !d.querySelector('[data-bpl]')) return;
249
+ const container = d.createElement('div');
250
+ container.className = 'bpl_page_bar';
251
+ container.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:2147483647';
252
+ d.body.append(container);
253
+ const input = attach(monaco, container, true), bar = container.querySelector('.ngn_lb');
254
+ bar.insertAdjacentHTML('afterbegin', '<button class="ngn_x" style="float:right" title="Close symbol bar">×</button><button class="ngn_o" style="float:right" title="Toggle overlay/push-down"></button>');
255
+ let overlay = false;
256
+ const padding = d.body.style.paddingTop, toggle = bar.querySelector('.ngn_o');
257
+ try { overlay = localStorage.getItem('ngn_lb_overlay') === '1'; } catch {}
258
+ function reflow() {
259
+ toggle.textContent = overlay ? '▼' : '▲';
260
+ d.body.style.paddingTop = overlay || container.hidden ? padding : `${bar.offsetHeight}px`;
261
+ }
262
+ bar.querySelector('.ngn_x').onclick = () => { container.hidden = true; reflow(); };
263
+ toggle.onclick = () => {
264
+ overlay = !overlay;
265
+ try { localStorage.setItem('ngn_lb_overlay', overlay ? '1' : '0'); } catch {}
266
+ reflow();
267
+ };
268
+ const observer = new ResizeObserver(reflow);
269
+ observer.observe(bar); reflow();
270
+ return {dispose() { input.dispose(); observer.disconnect(); container.remove(); d.body.style.paddingTop = padding; }};
271
+ }
272
+ return {attach, page};
273
+ })
274
+ ([{"glyph": "←", "name": "left-arrow", "kind": "syntax", "monad": "assign", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["是", "是"], "shortcut": " h"}, {"glyph": "⎕", "name": "quad", "kind": "syntax", "monad": "io", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["出入", "出入"], "shortcut": " q"}, {"glyph": "•", "name": "bullet", "kind": "system", "monad": "system", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["系", "系"], "shortcut": " y"}, {"glyph": "⍺", "name": "alpha", "kind": "argument", "monad": "left-argument", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["甲", "甲"], "shortcut": " a"}, {"glyph": "⍵", "name": "omega", "kind": "argument", "monad": "right-argument", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["乙", "乙"], "shortcut": " w"}, {"glyph": "⍶", "name": "alpha-underbar", "kind": "argument", "monad": "left-operand", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["丙", "丙"], "shortcut": " - a"}, {"glyph": "⍹", "name": "omega-underbar", "kind": "argument", "monad": "right-operand", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["丁", "丁"], "shortcut": " - w"}, {"glyph": "∇", "name": "del", "kind": "argument", "monad": "recursion", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["递", "遞"], "shortcut": " g"}, {"glyph": "⍢", "name": "del-diaeresis", "kind": "argument", "monad": "operator-recursion", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["循", "循"], "shortcut": " 1 g"}, {"glyph": "⍝", "name": "lamp", "kind": "comment", "monad": "comment", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["注", "注"], "shortcut": " o c"}, {"glyph": "⋄", "name": "diamond", "kind": "syntax", "monad": "separator", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["句", "句"], "shortcut": " `"}, {"glyph": "⁝", "name": "tricolon", "kind": "syntax", "monad": "token-separator", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["隔", "隔"], "shortcut": " :"}, {"glyph": "?", "name": "question", "kind": "syntax", "monad": "predicate", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["若", "若"], "shortcut": ""}, {"glyph": "¯", "name": "overbar", "kind": "literal", "monad": "negative", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["负", "負"], "shortcut": " -"}, {"glyph": "∞", "name": "infinity", "kind": "literal", "monad": "", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["永", "永"], "shortcut": " 8"}, {"glyph": "⍬", "name": "zilde", "kind": "literal", "monad": "empty", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["空", "空"], "shortcut": " )"}, {"glyph": "+", "name": "plus", "kind": "function", "monad": "conjugate", "dyad": "add", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["加", "加"], "shortcut": ""}, {"glyph": "-", "name": "minus", "kind": "function", "monad": "negate", "dyad": "subtract", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["减", "減"], "shortcut": ""}, {"glyph": "×", "name": "times", "kind": "function", "monad": "sign", "dyad": "multiply", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["乘号", "乘號"], "shortcut": " m"}, {"glyph": "÷", "name": "obelus", "kind": "function", "monad": "reciprocal", "dyad": "divide", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["除", "除"], "shortcut": " u"}, {"glyph": "⌈", "name": "upstile", "kind": "function", "monad": "ceiling", "dyad": "maximum", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["上", "上"], "shortcut": " s"}, {"glyph": "⌊", "name": "downstile", "kind": "function", "monad": "floor", "dyad": "minimum", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["下", "下"], "shortcut": " d"}, {"glyph": "|", "name": "stile", "kind": "function", "monad": "magnitude", "dyad": "residue", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["绝余", "絕餘"], "shortcut": ""}, {"glyph": "*", "name": "star", "kind": "function", "monad": "exponential", "dyad": "exponent", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["幂", "冪"], "shortcut": ""}, {"glyph": "⍟", "name": "circle-star", "kind": "function", "monad": "natural-log", "dyad": "logarithm", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["对", "對"], "shortcut": " o 8"}, {"glyph": "○", "name": "circle", "kind": "function", "monad": "cis", "dyad": "circular", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["圆", "圓"], "shortcut": " o"}, {"glyph": "π", "name": "pi", "kind": "function", "monad": "pi-times", "dyad": "pi-ratio", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["周", "周"], "shortcut": " p"}, {"glyph": "√", "name": "radical", "kind": "function", "monad": "sqrt", "dyad": "root", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["根", "根"], "shortcut": " 3"}, {"glyph": "!", "name": "exclamation", "kind": "function", "monad": "factorial", "dyad": "binomial", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["阶拣", "階揀"], "shortcut": ""}, {"glyph": "∧", "name": "wedge", "kind": "function", "monad": "polar", "dyad": "and", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["极且", "極且"], "shortcut": " 0"}, {"glyph": "∨", "name": "vee", "kind": "function", "monad": "real-imag", "dyad": "or", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["析或", "析或"], "shortcut": " 9"}, {"glyph": "⊼", "name": "wedge-bar", "kind": "function", "monad": "square", "dyad": "nand", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["方否", "方否"], "shortcut": " - 0"}, {"glyph": "⊽", "name": "vee-bar", "kind": "function", "monad": "double", "dyad": "nor", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["双莫", "雙莫"], "shortcut": " - 9"}, {"glyph": "~", "name": "tilde", "kind": "function", "monad": "not", "dyad": "without", "monad_rank": "0", "dyad_ranks": "∞ ∞", "hanzi": ["非无", "非無"], "shortcut": ""}, {"glyph": "=", "name": "equals", "kind": "function", "monad": "classify", "dyad": "equal", "monad_rank": "∞", "dyad_ranks": "0 0", "hanzi": ["等", "等"], "shortcut": ""}, {"glyph": "≠", "name": "not-equals", "kind": "function", "monad": "unique-mask", "dyad": "not-equal", "monad_rank": "∞", "dyad_ranks": "0 0", "hanzi": ["别初", "別初"], "shortcut": " +"}, {"glyph": "<", "name": "less-than", "kind": "function", "monad": "sort-up", "dyad": "less", "monad_rank": "∞", "dyad_ranks": "0 0", "hanzi": ["小", "小"], "shortcut": ""}, {"glyph": "≤", "name": "less-than-or-equal", "kind": "function", "monad": "decrement", "dyad": "less-equal", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["限退", "限退"], "shortcut": " <"}, {"glyph": ">", "name": "greater-than", "kind": "function", "monad": "sort-down", "dyad": "greater", "monad_rank": "∞", "dyad_ranks": "0 0", "hanzi": ["大", "大"], "shortcut": ""}, {"glyph": "≥", "name": "greater-than-or-equal", "kind": "function", "monad": "increment", "dyad": "greater-equal", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["达进", "達進"], "shortcut": " >"}, {"glyph": "⍳", "name": "iota", "kind": "function", "monad": "indices", "dyad": "index-of", "monad_rank": "1", "dyad_ranks": "∞ ∞", "hanzi": ["位", "位"], "shortcut": " i"}, {"glyph": "↦", "name": "right-arrow-from-bar", "kind": "function", "monad": "range", "dyad": "to", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["至", "至"], "shortcut": " l"}, {"glyph": ":", "name": "colon", "kind": "function", "monad": "unkey", "dyad": "keyed", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["键卸", "鍵卸"], "shortcut": ""}, {"glyph": "⍴", "name": "rho", "kind": "function", "monad": "shape", "dyad": "reshape", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["形", "形"], "shortcut": " r"}, {"glyph": "≢", "name": "equal-underbar-slash", "kind": "function", "monad": "tally", "dyad": "not-match", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["数异", "數異"], "shortcut": " ?"}, {"glyph": "≡", "name": "equal-underbar", "kind": "function", "monad": "depth", "dyad": "match", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["同深", "同深"], "shortcut": " ="}, {"glyph": ",", "name": "comma", "kind": "function", "monad": "ravel", "dyad": "catenate", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["连", "連"], "shortcut": ""}, {"glyph": "⍪", "name": "comma-bar", "kind": "function", "monad": "table", "dyad": "catenate-first", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["接矩", "接矩"], "shortcut": " - ,"}, {"glyph": "⊂", "name": "left-shoe", "kind": "function", "monad": "enclose", "dyad": "partitioned-enclose", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["包", "包"], "shortcut": " z"}, {"glyph": "⊃", "name": "right-shoe", "kind": "function", "monad": "mix", "dyad": "pick", "monad_rank": "0", "dyad_ranks": "1 ∞", "hanzi": ["选合", "選合"], "shortcut": " x"}, {"glyph": "⊆", "name": "left-shoe-underbar", "kind": "function", "monad": "nest", "dyad": "partition", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["巢段", "巢段"], "shortcut": " - z"}, {"glyph": "∊", "name": "epsilon", "kind": "function", "monad": "enlist", "dyad": "member", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["属展", "屬展"], "shortcut": " e"}, {"glyph": "∪", "name": "down-shoe", "kind": "function", "monad": "unique", "dyad": "union", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["并独", "併獨"], "shortcut": " v"}, {"glyph": "∩", "name": "up-shoe", "kind": "function", "monad": "duplicates", "dyad": "intersection", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["交重", "交重"], "shortcut": " c"}, {"glyph": "⍋", "name": "delta-stile", "kind": "function", "monad": "grade-up", "dyad": "grade-up-by", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["升", "升"], "shortcut": " \\ 7"}, {"glyph": "⍒", "name": "del-stile", "kind": "function", "monad": "grade-down", "dyad": "grade-down-by", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["降", "降"], "shortcut": " \\ g"}, {"glyph": "↑", "name": "up-arrow", "kind": "function", "monad": "first", "dyad": "take", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["取", "取"], "shortcut": " k"}, {"glyph": "↓", "name": "down-arrow", "kind": "function", "monad": "split", "dyad": "drop", "monad_rank": "1", "dyad_ranks": "1 ∞", "hanzi": ["去拆", "去拆"], "shortcut": " j"}, {"glyph": "⌽", "name": "circle-stile", "kind": "function", "monad": "reverse", "dyad": "rotate", "monad_rank": "1", "dyad_ranks": "0 1", "hanzi": ["转", "轉"], "shortcut": " 2"}, {"glyph": "⊖", "name": "circle-bar", "kind": "function", "monad": "reverse-first", "dyad": "rotate-first", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["纵", "縱"], "shortcut": " o -"}, {"glyph": "⍉", "name": "circle-backslash", "kind": "function", "monad": "transpose", "dyad": "reorder-axes", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["翻", "翻"], "shortcut": " o \\"}, {"glyph": "⊤", "name": "down-tack", "kind": "function", "monad": "binary-encode", "dyad": "encode", "monad_rank": "∞", "dyad_ranks": "1 0", "hanzi": ["编", "編"], "shortcut": " n"}, {"glyph": "⊥", "name": "up-tack", "kind": "function", "monad": "binary-decode", "dyad": "decode", "monad_rank": "1", "dyad_ranks": "1 1", "hanzi": ["译", "譯"], "shortcut": " b"}, {"glyph": "⍎", "name": "hydrant", "kind": "function", "monad": "execute", "dyad": "execute-in", "monad_rank": "1", "dyad_ranks": "∞ ∞", "hanzi": ["行", "行"], "shortcut": " o b"}, {"glyph": "⍕", "name": "thorn", "kind": "function", "monad": "format", "dyad": "format-spec", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["文", "文"], "shortcut": " o n"}, {"glyph": "⌷", "name": "squad", "kind": "function", "monad": "materialise", "dyad": "index", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["索", "索"], "shortcut": " q \\"}, {"glyph": "⌹", "name": "domino", "kind": "function", "monad": "matrix-inverse", "dyad": "matrix-divide", "monad_rank": "2", "dyad_ranks": "∞ 2", "hanzi": ["解逆", "解逆"], "shortcut": " q u"}, {"glyph": "#", "name": "hash", "kind": "function", "monad": "runs", "dyad": "replicate", "monad_rank": "∞", "dyad_ranks": "1 ∞", "hanzi": ["复", "複"], "shortcut": ""}, {"glyph": "↕", "name": "up-down-arrow", "kind": "function", "monad": "pairs", "dyad": "windows", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["窗", "窗"], "shortcut": " K"}, {"glyph": "⍭", "name": "stile-tilde", "kind": "function", "monad": "prime", "dyad": "prime-mode", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["素", "素"], "shortcut": " \\"}, {"glyph": "⨸", "name": "circle-divide", "kind": "function", "monad": "factors", "dyad": "factor-spec", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["因", "因"], "shortcut": " o u"}, {"glyph": "⌻", "name": "quad-jot", "kind": "function", "monad": "roots", "dyad": "polyval", "monad_rank": "1", "dyad_ranks": "1 0", "hanzi": ["零值", "零值"], "shortcut": " q t"}, {"glyph": "⍸", "name": "iota-underbar", "kind": "function", "monad": "where", "dyad": "interval-index", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["处间", "處間"], "shortcut": " - i"}, {"glyph": "⍷", "name": "epsilon-underbar", "kind": "function", "monad": "groups", "dyad": "find", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["寻群", "尋群"], "shortcut": " - e"}, {"glyph": "⊢", "name": "right-tack", "kind": "function", "monad": "same", "dyad": "right", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["右", "右"], "shortcut": " '"}, {"glyph": "⊣", "name": "left-tack", "kind": "function", "monad": "same-left", "dyad": "left", "monad_rank": "∞", "dyad_ranks": "∞ ∞", "hanzi": ["左", "左"], "shortcut": " ;"}, {"glyph": "¿", "name": "inverted-question", "kind": "function", "monad": "roll", "dyad": "deal", "monad_rank": "0", "dyad_ranks": "0 0", "hanzi": ["掷抽", "擲抽"], "shortcut": " /"}, {"glyph": "¨", "name": "diaeresis", "kind": "monadic-operator", "monad": "each", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["遍", "遍"], "shortcut": " 1"}, {"glyph": "ᵇ", "name": "superscript-b", "kind": "monadic-operator", "monad": "bitwise", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["按", "按"], "shortcut": " 6 b"}, {"glyph": "⍨", "name": "tilde-diaeresis", "kind": "monadic-operator", "monad": "self", "dyad": "commute", "monad_rank": "", "dyad_ranks": "", "hanzi": ["己换", "己換"], "shortcut": " 1 `"}, {"glyph": "↣", "name": "right-tail-arrow", "kind": "dyadic-operator", "monad": "before", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["先", "先"], "shortcut": " ."}, {"glyph": "⍤", "name": "jot-diaeresis", "kind": "dyadic-operator", "monad": "rank", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["秩", "秩"], "shortcut": " 1 t"}, {"glyph": "∘", "name": "jot", "kind": "dyadic-operator", "monad": "atop", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["承", "承"], "shortcut": " t"}, {"glyph": "⍠", "name": "quad-colon", "kind": "dyadic-operator", "monad": "axis", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["轴", "軸"], "shortcut": " q ;"}, {"glyph": "⍥", "name": "circle-diaeresis", "kind": "dyadic-operator", "monad": "over", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["经", "經"], "shortcut": " 1 o"}, {"glyph": "↢", "name": "left-tail-arrow", "kind": "dyadic-operator", "monad": "after", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["后", "後"], "shortcut": " ,"}, {"glyph": ".", "name": "dot", "kind": "dyadic-operator", "monad": "determinant", "dyad": "inner-product", "monad_rank": "", "dyad_ranks": "", "hanzi": ["内", "內"], "shortcut": ""}, {"glyph": "⊗", "name": "circle-times", "kind": "monadic-operator", "monad": "outer-product", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["表", "表"], "shortcut": " o m"}, {"glyph": "⌸", "name": "quad-equal", "kind": "monadic-operator", "monad": "key", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["组", "組"], "shortcut": " q ="}, {"glyph": "⍣", "name": "star-diaeresis", "kind": "dyadic-operator", "monad": "power", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["次止", "次止"], "shortcut": " 1 8"}, {"glyph": "⇄", "name": "right-left-arrows", "kind": "monadic-operator", "monad": "optimize", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["优", "優"], "shortcut": " 4"}, {"glyph": "⊘", "name": "circle-slash", "kind": "dyadic-operator", "monad": "valences", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["兼", "兼"], "shortcut": " o /"}, {"glyph": "@", "name": "at", "kind": "dyadic-operator", "monad": "under", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["于", "於"], "shortcut": ""}, {"glyph": "∂", "name": "partial", "kind": "monadic-operator", "monad": "derivative", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["导梯", "導梯"], "shortcut": " f"}, {"glyph": "⍚", "name": "diamond-underbar", "kind": "dyadic-operator", "monad": "agenda", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["择", "擇"], "shortcut": " - `"}, {"glyph": "⌺", "name": "quad-diamond", "kind": "dyadic-operator", "monad": "stencil", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["邻", "鄰"], "shortcut": " q `"}, {"glyph": "/", "name": "slash", "kind": "monadic-operator", "monad": "reduce", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["总", "總"], "shortcut": ""}, {"glyph": "⌿", "name": "slash-bar", "kind": "monadic-operator", "monad": "reduce-first", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["汇", "匯"], "shortcut": " - /"}, {"glyph": "\\", "name": "backslash", "kind": "monadic-operator", "monad": "scan", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["累", "累"], "shortcut": ""}, {"glyph": "⍀", "name": "backslash-bar", "kind": "monadic-operator", "monad": "scan-first", "dyad": "", "monad_rank": "", "dyad_ranks": "", "hanzi": ["续", "續"], "shortcut": " - \\"}], ((symbols, layout) => {
275
+ const comments = new Set(symbols.filter(s => s.kind === 'comment').flatMap(s => [...s.glyph, ...s.hanzi.join('')]));
276
+ // US physical keys, before macOS Option or another layout transforms event.key.
277
+ const punctuation = {Backquote: '`~', Minus: '-_', Equal: '=+', BracketLeft: '[{', BracketRight: ']}',
278
+ Backslash: '\\|', Semicolon: ';:', Quote: "'\"", Comma: ',<', Period: '.>', Slash: '/?'};
279
+ function usKey({code, shiftKey}) {
280
+ if (/^Key[A-Z]$/.test(code)) return shiftKey ? code.slice(3) : code.slice(3).toLowerCase();
281
+ if (/^Digit[0-9]$/.test(code)) return shiftKey ? ')!@#$%^&*('[Number(code[5])] : code[5];
282
+ if (code === 'Space') return ' ';
283
+ return punctuation[code]?.[Number(shiftKey)];
284
+ }
285
+
286
+ // Composed input from `layout.json`, with the same rules as the REPL and the macOS layout. `pending` is the dead-key state
287
+ // that the next key completes.
288
+ let pending = null;
289
+ const act = action => typeof action === 'string' ? (pending = null, action) : (pending = action.state, '');
290
+ // What a key types: `{text, stop}`, where `stop` means the key itself does nothing more, or nothing for an ordinary key.
291
+ // `option` is whether an Option chord counts here.
292
+ function press(ev, option) {
293
+ const key = usKey(ev);
294
+ if (pending) {
295
+ const name = pending, state = layout.states[name];
296
+ pending = null;
297
+ if (ev.key === 'Backspace' || ev.key === 'Escape') return {text: '', stop: true};
298
+ if (!ev.ctrlKey && !ev.metaKey) {
299
+ if (key in state.keys) return {text: act(state.keys[key]), stop: true};
300
+ if (key === ' ' || layout.option[key]?.state === name) return {text: state.terminator, stop: true};
301
+ }
302
+ // Any other key types the terminator, then acts as if nothing were pending.
303
+ const rest = press(ev, option);
304
+ return {text: state.terminator + (rest?.text ?? ''), stop: rest?.stop ?? false};
305
+ }
306
+ // Native Option can hide the alias character; accept its US physical chord too.
307
+ const action = option && ev.altKey && !ev.ctrlKey && !ev.metaKey
308
+ ? layout.alt_aliases[ev.key] ?? (Object.values(layout.alt_aliases).includes(key) ? key : layout.option[key]) : null;
309
+ if (action) return {text: act(action), stop: true};
310
+ }
311
+ const reset = () => { pending = null; };
312
+
313
+ // Every name ranks alike at each level: exact, prefix, then prefixes of hyphen-separated parts. Each match carries the name it
314
+ // matched by, the glyph name where several of a glyph's names match equally, as in `matches` in `symbols.rs`.
315
+ function matches(query) {
316
+ query = query.toLowerCase();
317
+ const letters = word => word.replaceAll('-', '');
318
+ let best = Infinity, found = [];
319
+ for (const {glyph, name, monad, dyad} of symbols) {
320
+ let rank = Infinity, matched;
321
+ [name, monad, dyad].filter(Boolean).forEach(word => {
322
+ const level = letters(word) === query ? 0 : letters(word).startsWith(query) ? 1 : 2;
323
+ if (level === 2) {
324
+ let rest = query;
325
+ for (const part of word.split('-')) {
326
+ let n = 0;
327
+ while (n < part.length && part[n] === rest[n]) n++;
328
+ if (!n) break;
329
+ rest = rest.slice(n);
330
+ if (!rest) break;
331
+ }
332
+ if (rest) return;
333
+ }
334
+ if (level < rank) { rank = level; matched = word; }
335
+ });
336
+ if (rank < best) { best = rank; found = []; }
337
+ if (rank < Infinity && rank === best) found.push([glyph, matched]);
338
+ }
339
+ // A name that is a prefix of every other match wins: `om gives omega, `omu gives omega-underbar.
340
+ const shortest = found.find(([, a]) => found.every(([, b]) => letters(b).startsWith(letters(a))));
341
+ return shortest && found.length > 1 ? [shortest] : found;
342
+ }
343
+
344
+ function inCode(text, python = false) {
345
+ let quote = '', comment = false;
346
+ for (let i = 0; i < text.length; i++) {
347
+ const c = text[i];
348
+ if (comment) { if (c === '\n') comment = false; }
349
+ else if (quote) {
350
+ if (python && c === '\\') i++;
351
+ else if (text.startsWith(quote, i)) { i += quote.length - 1; quote = ''; }
352
+ } else if (c === "'" && !python) {
353
+ if (text.startsWith("''", i) && !text.startsWith("'''", i)) i++;
354
+ else {
355
+ if (i + 2 >= text.length) return false;
356
+ i += 2;
357
+ }
358
+ } else if (c === "'" || c === '"') {
359
+ quote = python && text.startsWith(c.repeat(3), i) ? c.repeat(3) : c;
360
+ i += quote.length - 1;
361
+ } else if (python ? c === '#' : comments.has(c)) comment = true;
362
+ }
363
+ return !quote && !comment;
364
+ }
365
+
366
+ function bplStart(e) {
367
+ if (e.bpl) return 0;
368
+ const header = /^%%(?:bpl|apl)[^\S\n]*(?:\r?\n|$)/.exec(e.text);
369
+ if (header) return e.pos >= header[0].length ? header[0].length : -1;
370
+ const start = e.text.lastIndexOf('\n', e.pos - 1) + 1;
371
+ const line = /^[ \t]*(?:[\p{ID_Start}_][\p{ID_Continue}]*[ \t]*=[ \t]*)?%bpl[ \t]+/u.exec(e.text.slice(start, e.pos));
372
+ return line && inCode(e.text.slice(0, start), true) ? start + line[0].length : -1;
373
+ }
374
+
375
+ function entry(e) {
376
+ const body = bplStart(e), start = e.text.lastIndexOf('`', e.pos - 1);
377
+ if (body < 0 || start < body || !e.empty || !inCode(e.text.slice(body, start))) return;
378
+ const query = e.text.slice(start + 1, e.pos);
379
+ if (/^[a-z]*$/i.test(query)) return {start, query, found: matches(query)};
380
+ }
381
+
382
+ return {matches, inCode, bplStart, entry, press, reset};
383
+ })
384
+ , {
385
+ "alt_aliases": {"£": "#"},
386
+ "unshifted": {"!": "1", "@": "2", "#": "3", "$": "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", ")": "0", "~": "`", "_": "-", "|": "\\", ":": ";"},
387
+ "optional_shift": {"!": "1", "@": "2", "#": "3", "$": "4", "%": "5", "^": "6", "&": "7", "*": "8", "(": "9", "_": "-"},
388
+ "option": {
389
+ "`": "⋄",
390
+ "1": {"state": "diaeresis"},
391
+ "2": "⌽",
392
+ "3": "√",
393
+ "4": "⇄",
394
+ "5": {"state": "subscript"},
395
+ "6": {"state": "superscript"},
396
+ "7": "∆",
397
+ "8": "∞",
398
+ "9": "∨",
399
+ "0": "∧",
400
+ ")": "⍬",
401
+ "!": "¹",
402
+ "@": "²",
403
+ "#": "³",
404
+ "$": "⁴",
405
+ "%": "⁵",
406
+ "^": "⁶",
407
+ "&": "⁷",
408
+ "*": "⁸",
409
+ "(": "⁹",
410
+ "_": "⁻",
411
+ "-": {"state": "bar"},
412
+ "=": "≡",
413
+ "q": {"state": "quad"},
414
+ "w": "⍵",
415
+ "e": "∊",
416
+ "r": "⍴",
417
+ "t": "∘",
418
+ "y": "•",
419
+ "u": "÷",
420
+ "i": "⍳",
421
+ "o": {"state": "circle"},
422
+ "p": "π",
423
+ "+": "≠",
424
+ "\\": {"state": "stile"},
425
+ "a": "⍺",
426
+ "s": "⌈",
427
+ "d": "⌊",
428
+ "f": "∂",
429
+ "g": "∇",
430
+ "h": "←",
431
+ "j": "↓",
432
+ "k": "↑",
433
+ "K": "↕",
434
+ "l": "↦",
435
+ ";": "⊣",
436
+ ":": "⁝",
437
+ "'": "⊢",
438
+ "z": "⊂",
439
+ "x": "⊃",
440
+ "c": "∩",
441
+ "v": "∪",
442
+ "b": "⊥",
443
+ "n": "⊤",
444
+ "m": "×",
445
+ ",": "↢",
446
+ ".": "↣",
447
+ "<": "≤",
448
+ ">": "≥",
449
+ "/": "¿",
450
+ "?": "≢"
451
+ },
452
+ "states": {
453
+ "diaeresis": {"terminator": "¨", "keys": {"t": "⍤", "o": "⍥", "*": "⍣", "~": "⍨", "g": "⍢", "8": "⍣", "`": "⍨"}},
454
+ "superscript": {"terminator": "⁶", "keys": {"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", ")": "⁰", "!": "¹", "@": "²", "#": "³", "$": "⁴", "%": "⁵", "^": "⁶", "&": "⁷", "*": "⁸", "(": "⁹", "-": {"state": "superscript-minus"}, "b": "ᵇ", "t": "ᵀ", "u": "ᵘ"}},
455
+ "superscript-minus": {"terminator": "⁻", "keys": {"0": "⁻⁰", "1": "⁻¹", "2": "⁻²", "3": "⁻³", "4": "⁻⁴", "5": "⁻⁵", "6": "⁻⁶", "7": "⁻⁷", "8": "⁻⁸", "9": "⁻⁹", ")": "⁻⁰", "!": "⁻¹", "@": "⁻²", "#": "⁻³", "$": "⁻⁴", "%": "⁻⁵", "^": "⁻⁶", "&": "⁻⁷", "*": "⁻⁸", "(": "⁻⁹"}},
456
+ "subscript": {"terminator": "₅", "keys": {"0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄", "5": "₅", "6": "₆", "7": "₇", "8": "₈", "9": "₉", ")": "₀", "!": "₁", "@": "₂", "#": "₃", "$": "₄", "%": "₅", "^": "₆", "&": "₇", "*": "₈", "(": "₉", "x": "ₓ", "j": "ⱼ", "e": "ₑ", "r": "ᵣ", "h": "ₕ", "s": "ₛ", "p": "ₚ", "-": {"state": "subscript-minus"}}},
457
+ "subscript-minus": {"terminator": "₋", "keys": {"0": "₋₀", "1": "₋₁", "2": "₋₂", "3": "₋₃", "4": "₋₄", "5": "₋₅", "6": "₋₆", "7": "₋₇", "8": "₋₈", "9": "₋₉", ")": "₋₀", "!": "₋₁", "@": "₋₂", "#": "₋₃", "$": "₋₄", "%": "₋₅", "^": "₋₆", "&": "₋₇", "*": "₋₈", "(": "₋₉"}},
458
+ "bar": {"terminator": "¯", "keys": {",": "⍪", "\\": "⍀", "/": "⌿", "a": "⍶", "w": "⍹", "7": "⍙", "`": "⍚", "0": "⊼", "9": "⊽", "e": "⍷", "i": "⍸", "z": "⊆"}},
459
+ "quad": {"terminator": "⎕", "keys": {"t": "⌻", ":": "⍠", "=": "⌸", "u": "⌹", "|": "⌷", "`": "⌺", ";": "⍠", "\\": "⌷"}},
460
+ "circle": {"terminator": "○", "keys": {"-": "⊖", "\\": "⍉", "/": "⊘", "*": "⍟", "m": "⊗", "u": "⨸", "8": "⍟", "c": "⍝", "b": "⍎", "n": "⍕"}},
461
+ "stile": {"terminator": "⍭", "keys": {"7": "⍋", "g": "⍒"}}
462
+ }
463
+ }
464
+ );
465
+ const font = new URL("SAX2B.woff2", import.meta.url);
466
+ export function attach(monaco, container) {
467
+ const input = bar.attach(monaco, container), style = container.ownerDocument.createElement('style');
468
+ style.textContent = `@font-face{font-family:SAX2B;src:url("${font}") format("woff2")}`;
469
+ container.append(style);
470
+ return {dispose() { input.dispose(); style.remove(); }};
471
+ }
@@ -45,7 +45,7 @@
45
45
  "name": "string.quoted.single.bpl"
46
46
  },
47
47
  {
48
- "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←⎕•⍺⍵⍶⍹∇⍢⍝⋄\\?¯∞⍬\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿¨ᵇ⍨↣⍤∘⍠⍥↢\\.⊗⌸⍣⇄⊘@∂⍚⌺/⌿\\\\⍀⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+",
48
+ "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←⎕•⍺⍵⍶⍹∇⍢⍝⋄⁝\\?¯∞⍬\\+\\-×÷⌈⌊\\|\\*⍟○π√!∧∨⊼⊽\\~=≠<≤>≥⍳↦:⍴≢≡,⍪⊂⊃⊆∊∪∩⍋⍒↑↓⌽⊖⍉⊤⊥⍎⍕⌷⌹\\#↕⍭⨸⌻⍸⍷⊢⊣¿¨ᵇ⍨↣⍤∘⍠⍥↢\\.⊗⌸⍣⇄⊘@∂⍚⌺/⌿\\\\⍀⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+",
49
49
  "name": "variable.other.bpl"
50
50
  },
51
51
  {
@@ -106,7 +106,7 @@
106
106
  "name": "variable.parameter.bpl"
107
107
  },
108
108
  {
109
- "match": "[←⎕⋄\\?₀₁₂₃₄₅₆₇₈₉₋]",
109
+ "match": "[←⎕⋄⁝\\?₀₁₂₃₄₅₆₇₈₉₋]",
110
110
  "name": "keyword.control.bpl"
111
111
  }
112
112
  ]
@@ -19,7 +19,7 @@
19
19
  "applyEndPatternLast": true
20
20
  },
21
21
  {
22
- "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←是⎕出入•系⍺甲⍵乙⍶丙⍹丁∇递遞⍢循⍝注⋄句\\?若¯负負∞永⍬空\\+加\\-减減×乘号號÷除⌈上⌊下\\|绝余絕餘\\*幂冪⍟对對○圆圓π周√根!阶拣階揀∧极且極∨析或⊼方否⊽双莫雙\\~非无無=等≠别初別<小≤限退>大≥达进達進⍳位↦至:键卸鍵⍴形≢数异數異≡同深,连連⍪接矩⊂包⊃选合選⊆巢段∊属展屬∪并独併獨∩交重⍋升⍒降↑取↓去拆⌽转轉⊖纵縱⍉翻⊤编編⊥译譯⍎行⍕文⌷索⌹解逆\\#复複↕窗⍭素⨸因⌻零值⍸处间處間⍷寻群尋⊢右⊣左¿掷抽擲¨遍ᵇ按⍨己换換↣先⍤秩∘承⍠轴軸⍥经經↢后後\\.内內⊗表⌸组組⍣次止⇄优優⊘兼@于於∂导梯導⍚择擇⌺邻鄰/总總⌿汇匯\\\\累⍀续續⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+",
22
+ "match": "(?:_\\d+|_\\S|_|[^\\s\\p{Cc}0-9←是⎕出入•系⍺甲⍵乙⍶丙⍹丁∇递遞⍢循⍝注⋄句⁝隔\\?若¯负負∞永⍬空\\+加\\-减減×乘号號÷除⌈上⌊下\\|绝余絕餘\\*幂冪⍟对對○圆圓π周√根!阶拣階揀∧极且極∨析或⊼方否⊽双莫雙\\~非无無=等≠别初別<小≤限退>大≥达进達進⍳位↦至:键卸鍵⍴形≢数异數異≡同深,连連⍪接矩⊂包⊃选合選⊆巢段∊属展屬∪并独併獨∩交重⍋升⍒降↑取↓去拆⌽转轉⊖纵縱⍉翻⊤编編⊥译譯⍎行⍕文⌷索⌹解逆\\#复複↕窗⍭素⨸因⌻零值⍸处间處間⍷寻群尋⊢右⊣左¿掷抽擲¨遍ᵇ按⍨己换換↣先⍤秩∘承⍠轴軸⍥经經↢后後\\.内內⊗表⌸组組⍣次止⇄优優⊘兼@于於∂导梯導⍚择擇⌺邻鄰/总總⌿汇匯\\\\累⍀续續⁰¹²³⁴⁵⁶⁷⁸⁹⁻ᵀᵘₓ₀₁₂₃₄₅₆₇₈₉₋ₓ₁ₓ₂ₓ₄ₓ₈ₓₕₛₚ\\(\\)\\[\\]\\{\\}'\";\\$_ₑⱼᵣ])+",
23
23
  "name": "variable.other.bpl"
24
24
  },
25
25
  {
@@ -51,7 +51,7 @@
51
51
  "name": "variable.parameter.bpl"
52
52
  },
53
53
  {
54
- "match": "[是出入句若]",
54
+ "match": "[是出入句隔若]",
55
55
  "name": "keyword.control.bpl"
56
56
  },
57
57
  {
package/layout.json CHANGED
@@ -50,6 +50,7 @@
50
50
  "K": "↕",
51
51
  "l": "↦",
52
52
  ";": "⊣",
53
+ ":": "⁝",
53
54
  "'": "⊢",
54
55
  "z": "⊂",
55
56
  "x": "⊃",
package/lb.js CHANGED
@@ -2,235 +2,268 @@
2
2
  // MIT License, Copyright (c) 2011-2020 Nikolay G. Nikolov and Adam Brudzevski.
3
3
  // BPL name completion, editor adapters, dark mode and overlay layout by Jeremy Howard.
4
4
  ((symbols, input, layout) => {
5
- const d = document;
6
- // Docs pages have no bar, except a page with a `data-bpl` editor, such as the playground.
7
- if (d.querySelector('.ngn_lb') || d.querySelector('meta[name=generator][content^=quarto]') && !d.querySelector('[data-bpl]')) return;
5
+ function attach(monaco, container, magics = false) {
6
+ const d = container.ownerDocument, w = d.defaultView, events = new AbortController();
7
+ const on = (target, name, fn, capture = false) => target.addEventListener(name, fn, {capture, signal: events.signal});
8
+ const {inCode, bplStart, entry, press, reset} = input(symbols, layout);
9
+ const shortcuts = new Map(symbols.map(({glyph, shortcut}) => [glyph, shortcut]));
10
+ let leftAlt = false, rightAlt = false;
8
11
 
9
- const {inCode, bplStart, entry, press, reset} = input(symbols, layout);
10
- const shortcuts = new Map(symbols.map(({glyph, shortcut}) => [glyph, shortcut]));
11
- let leftAlt = false, rightAlt = false;
12
-
13
- function textareaRect(t) {
14
- const mirror = d.createElement('div'), caret = d.createElement('span'), css = getComputedStyle(t), rect = t.getBoundingClientRect();
15
- for (const p of ['font', 'lineHeight', 'letterSpacing', 'padding', 'border', 'boxSizing', 'width', 'tabSize']) mirror.style[p] = css[p];
16
- Object.assign(mirror.style, {position: 'fixed', visibility: 'hidden', whiteSpace: 'pre-wrap', overflowWrap: 'break-word',
17
- left: `${rect.left - t.scrollLeft}px`, top: `${rect.top - t.scrollTop}px`});
18
- mirror.textContent = t.value.slice(0, t.selectionStart);
19
- caret.textContent = '\u200b';
20
- mirror.append(caret);
21
- d.body.append(mirror);
22
- const result = caret.getBoundingClientRect();
23
- mirror.remove();
24
- return result;
25
- }
26
-
27
- function editor(target) {
28
- if (target.closest?.('.text_cell, .jp-MarkdownCell')) return;
29
- const m = window.monaco?.editor?.getEditors?.().find(e => e.hasTextFocus());
30
- if (m) {
31
- const model = m.getModel(), p = m.getPosition(), selection = m.getSelection();
32
- if (model.getLanguageId() === 'markdown' || m.getOption(window.monaco.editor.EditorOption.readOnly)) return;
33
- return {id: m, text: model.getValue(), pos: model.getOffsetAt(p), empty: selection.isEmpty(), bpl: model.getLanguageId() === 'bpl',
34
- rect: () => {
35
- const r = m.getDomNode().getBoundingClientRect(), c = m.getScrolledVisiblePosition(p);
36
- return {left: r.left + c.left, bottom: r.top + c.top + c.height};
37
- },
38
- insert: (text, from = model.getOffsetAt(selection.getStartPosition())) => {
39
- const a = model.getPositionAt(from), b = selection.getEndPosition();
40
- m.pushUndoStop();
41
- m.executeEdits('aplnb', [{range: {startLineNumber: a.lineNumber, startColumn: a.column,
42
- endLineNumber: b.lineNumber, endColumn: b.column}, text}], () => {
43
- const end = model.getPositionAt(from + text.length);
44
- return [new window.monaco.Selection(end.lineNumber, end.column, end.lineNumber, end.column)];
45
- });
46
- m.pushUndoStop();
47
- m.focus();
48
- }};
12
+ function textareaRect(t) {
13
+ const mirror = d.createElement('div'), caret = d.createElement('span'), css = getComputedStyle(t), rect = t.getBoundingClientRect();
14
+ for (const p of ['font', 'lineHeight', 'letterSpacing', 'padding', 'border', 'boxSizing', 'width', 'tabSize']) mirror.style[p] = css[p];
15
+ Object.assign(mirror.style, {position: 'fixed', visibility: 'hidden', whiteSpace: 'pre-wrap', overflowWrap: 'break-word',
16
+ left: `${rect.left - t.scrollLeft}px`, top: `${rect.top - t.scrollTop}px`});
17
+ mirror.textContent = t.value.slice(0, t.selectionStart);
18
+ caret.textContent = '\u200b';
19
+ mirror.append(caret);
20
+ d.body.append(mirror);
21
+ const result = caret.getBoundingClientRect();
22
+ mirror.remove();
23
+ return result;
49
24
  }
50
- const view = target.closest?.('.cm-editor')?.querySelector('.cm-content')?.cmTile?.view;
51
- if (view) {
52
- if (view.state.readOnly) return;
53
- const sel = view.state.selection.main;
54
- return {id: view, text: view.state.doc.toString(), pos: sel.head, empty: sel.empty,
55
- rect: () => view.coordsAtPos(sel.head),
56
- insert: (text, from = sel.from) => {
57
- view.dispatch({changes: {from, to: sel.to, insert: text}, selection: {anchor: from + text.length}, userEvent: 'input.complete'});
58
- view.focus();
25
+
26
+ function editor(target) {
27
+ if (target.closest?.('.text_cell, .jp-MarkdownCell')) return;
28
+ const m = monaco?.editor.getEditors().find(e => e.hasTextFocus());
29
+ if (m) {
30
+ const model = m.getModel(), p = m.getPosition(), selection = m.getSelection();
31
+ if (!model || !p || !selection) return;
32
+ const valid = () => m.getDomNode()?.isConnected && m.getModel() === model && !m.getOption(monaco.editor.EditorOption.readOnly)
33
+ && (model.getLanguageId() === 'bpl' || magics && bplStart({text: model.getValue(), pos: model.getOffsetAt(m.getPosition())}) >= 0);
34
+ if (!valid()) return;
35
+ const e = {id: model, valid, text: model.getValue(), pos: model.getOffsetAt(p), empty: selection.isEmpty(), bpl: model.getLanguageId() === 'bpl',
36
+ rect: () => {
37
+ const r = m.getDomNode().getBoundingClientRect(), c = m.getScrolledVisiblePosition(p);
38
+ return {left: r.left + c.left, bottom: r.top + c.top + c.height};
39
+ },
40
+ insert: (text, from) => {
41
+ if (!valid()) return;
42
+ const selection = m.getSelection();
43
+ from ??= model.getOffsetAt(selection.getStartPosition());
44
+ const a = model.getPositionAt(from), b = selection.getEndPosition();
45
+ m.pushUndoStop();
46
+ m.executeEdits('aplnb', [{range: {startLineNumber: a.lineNumber, startColumn: a.column,
47
+ endLineNumber: b.lineNumber, endColumn: b.column}, text}], () => {
48
+ const end = model.getPositionAt(from + text.length);
49
+ return [new monaco.Selection(end.lineNumber, end.column, end.lineNumber, end.column)];
50
+ });
51
+ m.pushUndoStop();
52
+ m.focus();
53
+ }};
54
+ return bplStart(e) >= 0 ? e : undefined;
55
+ }
56
+ if (!magics) return;
57
+ const view = target.closest?.('.cm-editor')?.querySelector('.cm-content')?.cmTile?.view;
58
+ if (view) {
59
+ if (view.state.readOnly) return;
60
+ const sel = view.state.selection.main;
61
+ const e = {id: view, valid: () => view.dom.isConnected && !view.state.readOnly
62
+ && bplStart({text: view.state.doc.toString(), pos: view.state.selection.main.head}) >= 0,
63
+ text: view.state.doc.toString(), pos: sel.head, empty: sel.empty,
64
+ rect: () => view.coordsAtPos(sel.head),
65
+ insert: (text, from) => {
66
+ if (!e.valid()) return;
67
+ const sel = view.state.selection.main;
68
+ from ??= sel.from;
69
+ view.dispatch({changes: {from, to: sel.to, insert: text}, selection: {anchor: from + text.length}, userEvent: 'input.complete'});
70
+ view.focus();
71
+ }};
72
+ return bplStart(e) >= 0 ? e : undefined;
73
+ }
74
+ if (target.tagName !== 'TEXTAREA' || target.readOnly || target.disabled) return;
75
+ const e = {id: target, valid: () => target.isConnected && !target.readOnly && !target.disabled
76
+ && bplStart({text: target.value, pos: target.selectionStart, bpl: target.hasAttribute('data-bpl')}) >= 0,
77
+ text: target.value, pos: target.selectionStart, empty: target.selectionStart === target.selectionEnd, bpl: target.hasAttribute('data-bpl'),
78
+ rect: () => textareaRect(target),
79
+ insert: (text, from = target.selectionStart) => {
80
+ if (!e.valid()) return;
81
+ target.focus();
82
+ target.selectionStart = from;
83
+ d.execCommand('insertText', false, text);
59
84
  }};
85
+ return bplStart(e) >= 0 ? e : undefined;
60
86
  }
61
- if (target.tagName !== 'TEXTAREA' || target.readOnly || target.disabled) return;
62
- return {id: target, text: target.value, pos: target.selectionStart, empty: target.selectionStart === target.selectionEnd, bpl: target.hasAttribute('data-bpl'),
63
- rect: () => textareaRect(target),
64
- insert: (text, from = target.selectionStart) => {
65
- target.focus();
66
- target.selectionStart = from;
67
- d.execCommand('insertText', false, text);
68
- }};
69
- }
70
87
 
71
- const host = d.createElement('div');
72
- host.innerHTML = `<div class="ngn_lb" aria-label="BPL symbols"><button class="ngn_x" title="Close symbol bar">×</button><button class="ngn_o" title="Toggle overlay/push-down"></button></div>
73
- <div class="bpl_choices" role="group" aria-label="BPL symbol completions" hidden></div>
74
- <style>
75
- .ngn_lb,.bpl_choices{background:#eee;color:#111;font:15px var(--bpl-font),monospace;z-index:2147483647}
76
- .ngn_lb{position:fixed;top:0;left:0;right:0;border-bottom:1px solid #999;padding:2px}
77
- .ngn_lb button,.bpl_choices button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:2px 4px}
78
- .ngn_lb button:hover,.bpl_choices button:hover{background:#777;color:white}
79
- .ngn_x,.ngn_o{float:right}
80
- .bpl_choices{position:fixed;max-height:240px;max-width:calc(100vw - 16px);overflow:auto;border:1px solid #888;border-radius:4px;box-shadow:0 3px 12px #0003;padding:4px}
81
- .bpl_choices button{display:block;width:100%;text-align:left;white-space:nowrap}
82
- .bpl_choices small{display:block;padding:4px}
83
- .ngn_lb .bpl_dead::after{content:'▾';font-size:9px;margin-left:1px}
84
- .bpl_choices.bpl_menu{max-width:min(320px,calc(100vw - 16px))}
85
- .bpl_menu button{display:inline-block;width:auto;min-width:28px;text-align:center}
86
- @media(prefers-color-scheme:dark){.ngn_lb,.bpl_choices{background:#222;color:#ddd}.ngn_lb button:hover,.bpl_choices button:hover{background:#bbb;color:#111}}
87
- </style>`;
88
- d.body.append(host);
89
- const bar = host.querySelector('.ngn_lb'), tip = host.querySelector('.bpl_choices'), toggle = bar.querySelector('.ngn_o');
90
- let overlay = false, active, lastEditor, choice, keyInput = false;
91
- const originalPadding = d.body.style.paddingTop;
92
- try { overlay = localStorage.getItem('ngn_lb_overlay') === '1'; } catch {}
93
- function reflow() {
94
- toggle.textContent = overlay ? '▼' : '▲';
95
- d.body.style.paddingTop = overlay || bar.hidden ? originalPadding : `${bar.offsetHeight}px`;
96
- }
97
- const names = new Map(symbols.map(({glyph, name, monad, dyad}) =>
98
- [glyph, [name, monad, dyad].filter(Boolean).join(' ')]));
99
- function stateGlyphs(state) {
100
- const {terminator, keys} = layout.states[state];
101
- return [terminator, ...Object.values(keys).flatMap(value => typeof value === 'string' ? [value] : stateGlyphs(value.state))];
102
- }
103
- const groups = new Map(Object.values(layout.option).filter(value => value.state).map(({state}) =>
104
- [layout.states[state].terminator, [...new Set(stateGlyphs(state))]]));
105
- const variants = new Set([...groups.values()].flatMap(glyphs => glyphs.slice(1)));
106
- function button(glyph, name) {
107
- const b = d.createElement('button');
108
- b.type = 'button'; b.textContent = glyph; b.title = (name || glyph) + (shortcuts.get(glyph) || ''); b.dataset.glyph = glyph;
109
- return b;
110
- }
111
- // Related glyphs stay together; arrows meet at the boundary between menus and plain buttons.
112
- const order = [...new Set([
113
- ...'^_¯¨○⎕≡⍭↑↓←↦↢↣⇄∨∧⍺⍵∆∇⋄∊⍳⊂⊃∩∪⊥⊤⊣⊢×÷⌊⌈≤≥≠√π', ...groups.keys(), ...names.keys()
114
- ])];
115
- for (const glyph of [...order.filter(g => groups.has(g)), ...order.filter(g => !groups.has(g))]) {
116
- if (!groups.has(glyph) && (variants.has(glyph) || /^[\x20-\x7e]+$/.test(glyph))) continue;
117
- const b = button(glyph, names.get(glyph));
118
- if (groups.has(glyph)) {
119
- b.textContent = {'^': '²', '_': '₂'}[glyph] || glyph;
120
- b.classList.add('bpl_dead');
121
- b.setAttribute('aria-expanded', 'false');
122
- b.title += ' (variants)';
88
+ const host = d.createElement('div');
89
+ host.innerHTML = `<div class="ngn_lb" aria-label="BPL symbols"></div>
90
+ <div class="bpl_choices" role="group" aria-label="BPL symbol completions" hidden></div>
91
+ <style>
92
+ .ngn_lb,.bpl_choices{background:var(--bpl-bar-background,#eee);color:var(--bpl-bar-color,#111);font:15px var(--bpl-font,SAX2B),monospace}
93
+ .ngn_lb{border-bottom:1px solid #999;padding:2px}
94
+ .ngn_lb button,.bpl_choices button{font:inherit;color:inherit;background:none;border:0;cursor:pointer;padding:2px 4px}
95
+ .ngn_lb button:hover,.bpl_choices button:hover{background:#777;color:white}
96
+ .bpl_choices{position:fixed;z-index:2147483647;max-height:240px;max-width:calc(100vw - 16px);overflow:auto;border:1px solid #888;border-radius:4px;box-shadow:0 3px 12px #0003;padding:4px}
97
+ .bpl_choices button{display:block;width:100%;text-align:left;white-space:nowrap}
98
+ .bpl_choices small{display:block;padding:4px}
99
+ .ngn_lb .bpl_dead::after{content:'▾';font-size:9px;margin-left:1px}
100
+ .bpl_choices.bpl_menu{max-width:min(320px,calc(100vw - 16px))}
101
+ .bpl_menu button{display:inline-block;width:auto;min-width:28px;text-align:center}
102
+ </style>`;
103
+ container.append(host);
104
+ const bar = host.querySelector('.ngn_lb'), tip = host.querySelector('.bpl_choices');
105
+ let active, lastEditor, choice, keyInput = false;
106
+ const names = new Map(symbols.map(({glyph, name, monad, dyad}) =>
107
+ [glyph, [name, monad, dyad].filter(Boolean).join(' ')]));
108
+ function stateGlyphs(state) {
109
+ const {terminator, keys} = layout.states[state];
110
+ return [terminator, ...Object.values(keys).flatMap(value => typeof value === 'string' ? [value] : stateGlyphs(value.state))];
123
111
  }
124
- bar.append(b);
125
- }
126
- new ResizeObserver(reflow).observe(bar);
127
- reflow();
128
-
129
- function cancel() {
130
- choice?.anchor?.setAttribute('aria-expanded', 'false');
131
- active = undefined; choice = undefined; tip.hidden = true;
132
- }
133
- function show(e, item, anchor) {
134
- tip.classList.toggle('bpl_menu', !!anchor);
135
- tip.setAttribute('aria-label', anchor ? 'BPL symbol variants' : 'BPL symbol completions');
136
- anchor?.setAttribute('aria-expanded', 'true');
137
- if (choice?.item.query !== item.query || choice?.anchor !== anchor) {
138
- tip.replaceChildren();
139
- for (const [glyph, name] of item.found) {
140
- const b = button(glyph, name);
141
- if (!anchor) b.textContent = `${glyph} ${b.title}`;
142
- tip.append(b);
143
- }
144
- if (!item.found.length) {
145
- const note = d.createElement('small'); note.textContent = 'Unknown symbol'; tip.append(note);
112
+ const groups = new Map(Object.values(layout.option).filter(value => value.state).map(({state}) =>
113
+ [layout.states[state].terminator, [...new Set(stateGlyphs(state))]]));
114
+ const variants = new Set([...groups.values()].flatMap(glyphs => glyphs.slice(1)));
115
+ function button(glyph, name) {
116
+ const b = d.createElement('button');
117
+ b.type = 'button'; b.textContent = glyph; b.title = (name || glyph) + (shortcuts.get(glyph) || ''); b.dataset.glyph = glyph;
118
+ return b;
119
+ }
120
+ // Related glyphs stay together; arrows meet at the boundary between menus and plain buttons.
121
+ const order = [...new Set([
122
+ ...'^_¯¨○⎕≡⍭↑↓←↦↢↣⇄∨∧⍺⍵∆∇⋄∊⍳⊂⊃∩∪⊥⊤⊣⊢×÷⌊⌈≤≥≠√π', ...groups.keys(), ...names.keys()
123
+ ])];
124
+ for (const glyph of [...order.filter(g => groups.has(g)), ...order.filter(g => !groups.has(g))]) {
125
+ if (!groups.has(glyph) && (variants.has(glyph) || /^[\x20-\x7e]+$/.test(glyph))) continue;
126
+ const b = button(glyph, names.get(glyph));
127
+ if (groups.has(glyph)) {
128
+ b.textContent = {'^': '²', '_': '₂'}[glyph] || glyph;
129
+ b.classList.add('bpl_dead');
130
+ b.setAttribute('aria-expanded', 'false');
131
+ b.title += ' (variants)';
146
132
  }
133
+ bar.append(b);
147
134
  }
148
- choice = {e, item, anchor};
149
- tip.hidden = false;
150
- const r = anchor ? anchor.getBoundingClientRect() : e.rect();
151
- tip.style.left = `${Math.max(4, Math.min(r.left, innerWidth - tip.offsetWidth - 8))}px`;
152
- tip.style.top = `${Math.max(bar.hidden || overlay ? 4 : bar.offsetHeight, Math.min(r.bottom + 4, innerHeight - tip.offsetHeight - 8))}px`;
153
- }
154
- function refresh(target) {
155
- const e = editor(target), item = e && entry(e);
156
- if (item && active?.id === e.id && active.start === item.start) show(e, item);
157
- else cancel();
158
- }
159
- bar.addEventListener('mousedown', ev => {
160
- ev.preventDefault();
161
- const b = ev.target.closest('button');
162
- const opened = choice?.anchor === b;
163
- cancel();
164
- if (b?.classList.contains('ngn_x')) { bar.hidden = true; reflow(); }
165
- else if (b === toggle) {
166
- overlay = !overlay;
167
- try { localStorage.setItem('ngn_lb_overlay', overlay ? '1' : '0'); } catch {}
168
- reflow();
169
- } else if (b?.dataset.glyph && lastEditor) {
170
- const glyph = b.dataset.glyph, group = groups.get(glyph);
171
- if (group) {
172
- if (!opened) show(lastEditor, {query: glyph, found: group.map(g => [g, names.get(g)])}, b);
173
- } else lastEditor.insert(glyph);
135
+ function cancel() {
136
+ choice?.anchor?.setAttribute('aria-expanded', 'false');
137
+ active = undefined; choice = undefined; tip.hidden = true;
174
138
  }
175
- });
176
- tip.addEventListener('mousedown', ev => {
177
- ev.preventDefault();
178
- const b = ev.target.closest('button');
179
- if (b && choice) choice.e.insert(b.dataset.glyph, choice.item.start);
180
- cancel();
181
- });
182
- d.addEventListener('focusin', ev => { lastEditor = editor(ev.target); cancel(); });
183
- d.addEventListener('pointerdown', ev => { if (!host.contains(ev.target)) cancel(); }, true);
184
- d.addEventListener('pointerup', ev => { if (!host.contains(ev.target)) lastEditor = editor(ev.target); });
185
- for (const event of ['paste', 'cut', 'compositionstart', 'focusout']) d.addEventListener(event, cancel, true);
186
- d.addEventListener('input', ev => {
187
- if (!keyInput || ev.inputType !== 'insertText' && ev.inputType !== 'deleteContentBackward') cancel();
188
- keyInput = false;
189
- requestAnimationFrame(() => { lastEditor = editor(ev.target); refresh(ev.target); });
190
- });
191
- window.addEventListener('blur', () => { leftAlt = rightAlt = false; cancel(); reset(); });
192
- window.addEventListener('focusin', reset, true);
193
- window.addEventListener('keyup', ev => {
194
- if (ev.code === 'AltLeft') leftAlt = false;
195
- if (ev.code === 'AltRight') rightAlt = false;
196
- keyInput = false; lastEditor = editor(ev.target); if (active) refresh(ev.target);
197
- }, true);
198
- window.addEventListener('keydown', ev => {
199
- if (ev.key === 'Escape' && choice?.anchor) {
200
- cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return;
139
+ function show(e, item, anchor) {
140
+ tip.classList.toggle('bpl_menu', !!anchor);
141
+ tip.setAttribute('aria-label', anchor ? 'BPL symbol variants' : 'BPL symbol completions');
142
+ anchor?.setAttribute('aria-expanded', 'true');
143
+ if (choice?.item.query !== item.query || choice?.anchor !== anchor) {
144
+ tip.replaceChildren();
145
+ for (const [glyph, name] of item.found) {
146
+ const b = button(glyph, name);
147
+ if (!anchor) b.textContent = `${glyph} ${b.title}`;
148
+ tip.append(b);
149
+ }
150
+ if (!item.found.length) {
151
+ const note = d.createElement('small'); note.textContent = 'Unknown symbol'; tip.append(note);
152
+ }
153
+ }
154
+ choice = {e, item, anchor};
155
+ tip.hidden = false;
156
+ const r = anchor ? anchor.getBoundingClientRect() : e.rect();
157
+ tip.style.left = `${Math.max(4, Math.min(r.left, w.innerWidth - tip.offsetWidth - 8))}px`;
158
+ tip.style.top = `${Math.max(4, Math.min(r.bottom + 4, w.innerHeight - tip.offsetHeight - 8))}px`;
201
159
  }
202
- keyInput = false;
203
- if (ev.code === 'AltLeft') leftAlt = true;
204
- if (ev.code === 'AltRight') rightAlt = true;
205
- if (['Shift', 'Control', 'Alt', 'Meta'].includes(ev.key)) return;
206
- const e = editor(ev.target);
207
- if (!e || ev.isComposing || ev.defaultPrevented) { cancel(); return; }
208
- lastEditor = e;
209
- const body = bplStart(e);
210
- const pressed = press(ev, leftAlt && !rightAlt && !ev.getModifierState('AltGraph') && body >= 0);
211
- if (pressed) {
212
- if (pressed.text) e.insert(pressed.text);
213
- if (pressed.stop) { cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return; }
160
+ function refresh(target) {
161
+ const e = editor(target), item = e && entry(e);
162
+ if (item && active?.id === e.id && active.start === item.start) show(e, item);
163
+ else cancel();
214
164
  }
215
- const item = entry(e), plain = !ev.ctrlKey && !ev.altKey && !ev.metaKey;
216
- const tab = ev.key === 'Tab' && plain && !ev.shiftKey, enter = ev.key === 'Enter';
217
- const typed = active?.id === e.id && active.start === item?.start;
218
- const delimiter = plain && ev.key.length === 1 && !/[a-z]/i.test(ev.key);
219
- if (item && (tab || typed && (enter || delimiter))) {
220
- if (item.found.length === 1) {
221
- e.insert(item.found[0][0], item.start);
222
- cancel();
223
- } else if (tab) {
224
- active = {id: e.id, start: item.start};
225
- show(e, item);
165
+ on(bar, 'mousedown', ev => {
166
+ ev.preventDefault();
167
+ const b = ev.target.closest('button');
168
+ const opened = choice?.anchor === b;
169
+ cancel();
170
+ if (!lastEditor?.valid()) lastEditor = undefined;
171
+ if (b?.dataset.glyph && lastEditor) {
172
+ const glyph = b.dataset.glyph, group = groups.get(glyph);
173
+ if (group) {
174
+ if (!opened) show(lastEditor, {query: glyph, found: group.map(g => [g, names.get(g)])}, b);
175
+ } else lastEditor.insert(glyph);
176
+ }
177
+ });
178
+ on(tip, 'mousedown', ev => {
179
+ ev.preventDefault();
180
+ const b = ev.target.closest('button');
181
+ if (b && choice?.e.valid()) choice.e.insert(b.dataset.glyph, choice.item.start);
182
+ cancel();
183
+ });
184
+ on(d, 'focusin', ev => { if (!host.contains(ev.target)) lastEditor = editor(ev.target); cancel(); });
185
+ on(d, 'pointerdown', ev => { if (!host.contains(ev.target)) cancel(); }, true);
186
+ on(d, 'pointerup', ev => { if (!host.contains(ev.target)) lastEditor = editor(ev.target); });
187
+ for (const event of ['paste', 'cut', 'compositionstart', 'focusout']) on(d, event, cancel, true);
188
+ on(d, 'input', ev => {
189
+ if (!keyInput || ev.inputType !== 'insertText' && ev.inputType !== 'deleteContentBackward') cancel();
190
+ keyInput = false;
191
+ w.requestAnimationFrame(() => { if (!events.signal.aborted) { lastEditor = editor(ev.target); refresh(ev.target); } });
192
+ });
193
+ on(w, 'blur', () => { leftAlt = rightAlt = false; lastEditor = undefined; cancel(); reset(); });
194
+ on(w, 'focusin', reset, true);
195
+ on(w, 'keyup', ev => {
196
+ if (ev.code === 'AltLeft') leftAlt = false;
197
+ if (ev.code === 'AltRight') rightAlt = false;
198
+ keyInput = false; lastEditor = editor(ev.target); if (active) refresh(ev.target);
199
+ }, true);
200
+ on(w, 'keydown', ev => {
201
+ if (ev.key === 'Escape' && choice?.anchor) {
202
+ cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return;
203
+ }
204
+ keyInput = false;
205
+ if (ev.code === 'AltLeft') leftAlt = true;
206
+ if (ev.code === 'AltRight') rightAlt = true;
207
+ if (['Shift', 'Control', 'Alt', 'Meta'].includes(ev.key)) return;
208
+ const e = editor(ev.target);
209
+ if (!e || ev.isComposing || ev.defaultPrevented) { lastEditor = undefined; cancel(); reset(); return; }
210
+ if (lastEditor && lastEditor.id !== e.id) { cancel(); reset(); }
211
+ lastEditor = e;
212
+ const body = bplStart(e);
213
+ const pressed = press(ev, leftAlt && !rightAlt && !ev.getModifierState('AltGraph') && body >= 0);
214
+ if (pressed) {
215
+ if (pressed.text) e.insert(pressed.text);
216
+ if (pressed.stop) { cancel(); ev.preventDefault(); ev.stopImmediatePropagation(); return; }
226
217
  }
227
- if (tab) { ev.preventDefault(); ev.stopImmediatePropagation(); return; }
218
+ const item = entry(e), plain = !ev.ctrlKey && !ev.altKey && !ev.metaKey;
219
+ const tab = ev.key === 'Tab' && plain && !ev.shiftKey, enter = ev.key === 'Enter';
220
+ const typed = active?.id === e.id && active.start === item?.start;
221
+ const delimiter = plain && ev.key.length === 1 && !/[a-z]/i.test(ev.key);
222
+ if (item && (tab || typed && (enter || delimiter))) {
223
+ if (item.found.length === 1) {
224
+ e.insert(item.found[0][0], item.start);
225
+ cancel();
226
+ } else if (tab) {
227
+ active = {id: e.id, start: item.start};
228
+ show(e, item);
229
+ }
230
+ if (tab) { ev.preventDefault(); ev.stopImmediatePropagation(); return; }
231
+ }
232
+ if (ev.key === '`' && plain) {
233
+ const updated = editor(ev.target), body = updated ? bplStart(updated) : -1;
234
+ if (updated && body >= 0 && updated.empty && inCode(updated.text.slice(body, updated.pos))) active = {id: updated.id, start: updated.pos};
235
+ else cancel();
236
+ } else if (!(typed && plain && (/^[a-z]$/i.test(ev.key) || ev.key === 'Backspace'))) cancel();
237
+ keyInput = plain && (ev.key.length === 1 || ev.key === 'Backspace');
238
+ }, true);
239
+ return {dispose() { events.abort(); reset(); lastEditor = undefined; cancel(); host.remove(); }};
240
+ }
241
+
242
+ function page(monaco) {
243
+ const d = document;
244
+ if (d.querySelector('.bpl_page_bar') || d.querySelector('meta[name=generator][content^=quarto]') && !d.querySelector('[data-bpl]')) return;
245
+ const container = d.createElement('div');
246
+ container.className = 'bpl_page_bar';
247
+ container.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:2147483647';
248
+ d.body.append(container);
249
+ const input = attach(monaco, container, true), bar = container.querySelector('.ngn_lb');
250
+ bar.insertAdjacentHTML('afterbegin', '<button class="ngn_x" style="float:right" title="Close symbol bar">×</button><button class="ngn_o" style="float:right" title="Toggle overlay/push-down"></button>');
251
+ let overlay = false;
252
+ const padding = d.body.style.paddingTop, toggle = bar.querySelector('.ngn_o');
253
+ try { overlay = localStorage.getItem('ngn_lb_overlay') === '1'; } catch {}
254
+ function reflow() {
255
+ toggle.textContent = overlay ? '▼' : '▲';
256
+ d.body.style.paddingTop = overlay || container.hidden ? padding : `${bar.offsetHeight}px`;
228
257
  }
229
- if (ev.key === '`' && plain) {
230
- const updated = editor(ev.target), body = bplStart(updated);
231
- if (body >= 0 && updated.empty && inCode(updated.text.slice(body, updated.pos))) active = {id: updated.id, start: updated.pos};
232
- else cancel();
233
- } else if (!(typed && plain && (/^[a-z]$/i.test(ev.key) || ev.key === 'Backspace'))) cancel();
234
- keyInput = plain && (ev.key.length === 1 || ev.key === 'Backspace');
235
- }, true);
258
+ bar.querySelector('.ngn_x').onclick = () => { container.hidden = true; reflow(); };
259
+ toggle.onclick = () => {
260
+ overlay = !overlay;
261
+ try { localStorage.setItem('ngn_lb_overlay', overlay ? '1' : '0'); } catch {}
262
+ reflow();
263
+ };
264
+ const observer = new ResizeObserver(reflow);
265
+ observer.observe(bar); reflow();
266
+ return {dispose() { input.dispose(); observer.disconnect(); container.remove(); d.body.style.paddingTop = padding; }};
267
+ }
268
+ return {attach, page};
236
269
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basedpl",
3
- "version": "0.1.75",
3
+ "version": "0.1.77",
4
4
  "description": "BasedPL, the Based-array Programming Language, is an APL-derived array language, borrowing ideas from J and BQN, with an emphasis on simple, consistent notation.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {