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 +0 -0
- package/basedpl_wasm.js +11 -0
- package/basedpl_wasm_bg.wasm +0 -0
- package/bpl-editor.js +471 -0
- package/bpl.tmLanguage.json +2 -2
- package/hpl.tmLanguage.json +2 -2
- package/layout.json +1 -0
- package/lb.js +251 -218
- package/package.json +1 -1
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}
|
package/basedpl_wasm_bg.wasm
CHANGED
|
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
|
+
}
|
package/bpl.tmLanguage.json
CHANGED
|
@@ -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
|
]
|
package/hpl.tmLanguage.json
CHANGED
|
@@ -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
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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.
|
|
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": {
|