@neuroplastio/xterm-addon-hotty 0.1.0-next.5 → 0.1.0-next.7
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/README.md +7 -4
- package/dist/hotty-xterm.js +98 -30
- package/dist/hotty-xterm.js.map +3 -3
- package/dist/types/keys.d.ts +32 -10
- package/dist/types/surface.d.ts +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,10 +114,13 @@ runs programs for whoever connects.
|
|
|
114
114
|
selection works as usual. A right click leaves focus where the browser
|
|
115
115
|
put it, so the context menu's Copy copies the surface's selection.
|
|
116
116
|
- **A text field's keys** (SPEC §10.2, §10.4) are the program's keymap.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
A key that types no text (not a character, or a character with Control
|
|
118
|
+
or Meta) is offered first as pressed, named from the DOM's event, before
|
|
119
|
+
xterm.js translates it: on a Mac, Option+Left is Alt+ArrowLeft, not the
|
|
120
|
+
ESC b the program would read. Otherwise the key goes through xterm.js's
|
|
121
|
+
keyboard handling, with what it would send the program kept back, and
|
|
122
|
+
is named from that, so a field sees the key the program would read, in
|
|
123
|
+
the encoding the program enabled. The field's keymap (the default, then each `data-keys` from the root to the
|
|
121
124
|
field) decides: an action, which the addon does itself on the field's
|
|
122
125
|
text with the editing commands, so the browser sends `input` and
|
|
123
126
|
`change` as for typing; a character, which the browser types; or the
|
package/dist/hotty-xterm.js
CHANGED
|
@@ -898,6 +898,7 @@ var ACTIONS = /* @__PURE__ */ new Set([
|
|
|
898
898
|
"page-down",
|
|
899
899
|
"input-start",
|
|
900
900
|
"input-end",
|
|
901
|
+
"select-all",
|
|
901
902
|
"newline",
|
|
902
903
|
"submit",
|
|
903
904
|
"program",
|
|
@@ -913,7 +914,21 @@ var ACTIONS = /* @__PURE__ */ new Set([
|
|
|
913
914
|
"scroll-end"
|
|
914
915
|
]);
|
|
915
916
|
var SCROLL_ACTIONS = new Set([...ACTIONS].filter((a) => a.startsWith("scroll-")));
|
|
916
|
-
var MULTILINE_ACTIONS = /* @__PURE__ */ new Set(["line-previous", "line-next", "page-up", "page-down", "
|
|
917
|
+
var MULTILINE_ACTIONS = /* @__PURE__ */ new Set(["line-previous", "line-next", "page-up", "page-down", "newline"]);
|
|
918
|
+
var MOVES = /* @__PURE__ */ new Set([
|
|
919
|
+
"char-backward",
|
|
920
|
+
"char-forward",
|
|
921
|
+
"word-backward",
|
|
922
|
+
"word-forward",
|
|
923
|
+
"line-start",
|
|
924
|
+
"line-end",
|
|
925
|
+
"line-previous",
|
|
926
|
+
"line-next",
|
|
927
|
+
"page-up",
|
|
928
|
+
"page-down",
|
|
929
|
+
"input-start",
|
|
930
|
+
"input-end"
|
|
931
|
+
]);
|
|
917
932
|
var ROW_ACTIONS = /* @__PURE__ */ new Set(["line-previous", "line-next", "page-up", "page-down"]);
|
|
918
933
|
var BACKWARD = /* @__PURE__ */ new Set(["char-backward", "word-backward", "line-start", "line-previous", "page-up", "input-start"]);
|
|
919
934
|
var INSERT = "insert";
|
|
@@ -921,7 +936,7 @@ var TERMINAL_KEYS = [
|
|
|
921
936
|
"ArrowLeft=char-backward Control+b=char-backward ArrowRight=char-forward Control+f=char-forward",
|
|
922
937
|
"Alt+ArrowLeft=word-backward Control+ArrowLeft=word-backward Alt+b=word-backward",
|
|
923
938
|
"Alt+ArrowRight=word-forward Control+ArrowRight=word-forward Alt+f=word-forward",
|
|
924
|
-
"Home=line-start
|
|
939
|
+
"Home=line-start End=line-end Control+e=line-end",
|
|
925
940
|
"Backspace=delete-char-backward Control+h=delete-char-backward",
|
|
926
941
|
"Delete=delete-char-forward Control+d=delete-char-forward",
|
|
927
942
|
"Alt+Backspace=delete-word-backward Control+w=delete-word-backward Control+Backspace=delete-word-backward",
|
|
@@ -930,6 +945,7 @@ var TERMINAL_KEYS = [
|
|
|
930
945
|
"ArrowUp=line-previous Control+p=line-previous ArrowDown=line-next Control+n=line-next",
|
|
931
946
|
"PageUp=page-up PageDown=page-down",
|
|
932
947
|
"Alt+<=input-start Control+Home=input-start Alt+>=input-end Control+End=input-end",
|
|
948
|
+
"Control+a=select-all",
|
|
933
949
|
"Control+m=newline"
|
|
934
950
|
].join(" ");
|
|
935
951
|
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
@@ -1181,6 +1197,15 @@ var Keymap = class {
|
|
|
1181
1197
|
if (isChar(value) && !mods.some((m) => m !== "Shift")) return INSERT;
|
|
1182
1198
|
return null;
|
|
1183
1199
|
}
|
|
1200
|
+
/** Whether the field selects with a key (SPEC §10.2, *Shift selects*):
|
|
1201
|
+
* it looks up a move, and the key's name has Shift. The field then moves
|
|
1202
|
+
* its caret and keeps the selection's anchor. */
|
|
1203
|
+
selects(name) {
|
|
1204
|
+
const k = parseKey(name);
|
|
1205
|
+
if (k === null) return false;
|
|
1206
|
+
const a = this.lookup(k);
|
|
1207
|
+
return a !== null && MOVES.has(a) && splitKey(k)[0].includes("Shift");
|
|
1208
|
+
}
|
|
1184
1209
|
};
|
|
1185
1210
|
function* bindingsOf(value) {
|
|
1186
1211
|
for (const b of value.split(/[ \t\n\f\r]+/)) {
|
|
@@ -1207,14 +1232,31 @@ function resolve(multiline, ...values) {
|
|
|
1207
1232
|
for (const [k, a] of [
|
|
1208
1233
|
["ArrowLeft", "char-backward"],
|
|
1209
1234
|
["ArrowRight", "char-forward"],
|
|
1235
|
+
["Control+ArrowLeft", "word-backward"],
|
|
1236
|
+
["Control+ArrowRight", "word-forward"],
|
|
1237
|
+
["Alt+ArrowLeft", "word-backward"],
|
|
1238
|
+
["Alt+ArrowRight", "word-forward"],
|
|
1210
1239
|
["Home", "line-start"],
|
|
1211
1240
|
["End", "line-end"],
|
|
1241
|
+
["Control+Home", "input-start"],
|
|
1242
|
+
["Control+End", "input-end"],
|
|
1212
1243
|
["Backspace", "delete-char-backward"],
|
|
1213
1244
|
["Delete", "delete-char-forward"],
|
|
1245
|
+
["Control+Backspace", "delete-word-backward"],
|
|
1246
|
+
["Control+Delete", "delete-word-forward"],
|
|
1247
|
+
["Alt+Backspace", "delete-word-backward"],
|
|
1248
|
+
["Alt+Delete", "delete-word-forward"],
|
|
1214
1249
|
["ArrowUp", "line-previous"],
|
|
1215
1250
|
["ArrowDown", "line-next"],
|
|
1216
1251
|
["PageUp", "page-up"],
|
|
1217
1252
|
["PageDown", "page-down"],
|
|
1253
|
+
["Control+a", "select-all"],
|
|
1254
|
+
["Meta+ArrowLeft", "line-start"],
|
|
1255
|
+
["Meta+ArrowRight", "line-end"],
|
|
1256
|
+
["Meta+ArrowUp", "input-start"],
|
|
1257
|
+
["Meta+ArrowDown", "input-end"],
|
|
1258
|
+
["Meta+Backspace", "delete-to-line-start"],
|
|
1259
|
+
["Meta+a", "select-all"],
|
|
1218
1260
|
["Enter", multiline ? "newline" : "submit"]
|
|
1219
1261
|
])
|
|
1220
1262
|
m.bind(k, a);
|
|
@@ -1241,10 +1283,16 @@ function wordForward(t, p) {
|
|
|
1241
1283
|
while (p < t.chars.length && !isSpace(t.chars[p])) p++;
|
|
1242
1284
|
return p;
|
|
1243
1285
|
}
|
|
1244
|
-
function plan(t, action, rows, goal = null) {
|
|
1286
|
+
function plan(t, action, rows, goal = null, extend = false) {
|
|
1245
1287
|
if (MULTILINE_ACTIONS.has(action) && !t.multiline) return { kind: "none" };
|
|
1246
1288
|
const n = t.chars.length;
|
|
1247
1289
|
const selected = t.start < t.end;
|
|
1290
|
+
if (action === "select-all") return { kind: "move", anchor: 0, to: n };
|
|
1291
|
+
if (extend && MOVES.has(action)) {
|
|
1292
|
+
const anchor = t.caret === t.start ? t.end : t.start;
|
|
1293
|
+
const moved = plan({ ...t, start: t.caret, end: t.caret }, action, rows, goal);
|
|
1294
|
+
return moved.kind === "move" ? { ...moved, anchor } : moved;
|
|
1295
|
+
}
|
|
1248
1296
|
if (action.startsWith("delete-")) {
|
|
1249
1297
|
if (selected) return { kind: "replace", from: t.start, to: t.end, text: "" };
|
|
1250
1298
|
const p2 = t.start;
|
|
@@ -2005,7 +2053,10 @@ var Surface = class {
|
|
|
2005
2053
|
return el === this.masked ? (this.resolver.get(el, "type") ?? "").toLowerCase() : el.type;
|
|
2006
2054
|
}
|
|
2007
2055
|
/**
|
|
2008
|
-
* A key in a text field
|
|
2056
|
+
* A key in a text field, offered at most twice (SPEC §10.4). A key that
|
|
2057
|
+
* types no text is offered first as pressed, named from the DOM's event,
|
|
2058
|
+
* before xterm.js translates it (on a Mac, Option+ArrowLeft is ESC b to
|
|
2059
|
+
* the program). Otherwise it is named as the program would read it: from
|
|
2009
2060
|
* what xterm.js would send the program for it, in the encoding the
|
|
2010
2061
|
* program enabled, read as SPEC §10.4 says. The field's keymap (the
|
|
2011
2062
|
* default, then each `data-keys` from the root to the field) says what
|
|
@@ -2014,10 +2065,13 @@ var Surface = class {
|
|
|
2014
2065
|
*/
|
|
2015
2066
|
fieldKey(e, el, multiline) {
|
|
2016
2067
|
if (this.host.browserKey(e)) return;
|
|
2017
|
-
const data = this.host.encodeKey(e);
|
|
2018
|
-
const keys = data === null ? [{ key: domKeyName(e), data: "" }] : inputKeys(data);
|
|
2019
2068
|
const keymap = this.keymapOf(el, multiline);
|
|
2069
|
+
const pressed = typesNoText(e) ? domKeyName(e) : null;
|
|
2070
|
+
const first = pressed !== null && keymap.lookup(pressed) !== null;
|
|
2071
|
+
const data = first ? "" : this.host.encodeKey(e);
|
|
2072
|
+
const keys = first ? [{ key: pressed, data: "" }] : data === null ? [{ key: domKeyName(e), data: "" }] : inputKeys(data);
|
|
2020
2073
|
const uses = keys.map((k) => k.key === null ? null : keymap.lookup(k.key));
|
|
2074
|
+
const extend = keys.map((k) => k.key !== null && keymap.selects(k.key));
|
|
2021
2075
|
if (uses.every((u) => u === null)) {
|
|
2022
2076
|
const program = keys.some((k) => k.key !== null && keymap.program(k.key));
|
|
2023
2077
|
if (!program && this.axes && this.scrollKey(e)) return;
|
|
@@ -2046,7 +2100,7 @@ var Surface = class {
|
|
|
2046
2100
|
} else if (use === "submit") {
|
|
2047
2101
|
this.goal = null;
|
|
2048
2102
|
submitFrom(el);
|
|
2049
|
-
} else this.edit(el, multiline, { kind: "action", action: use });
|
|
2103
|
+
} else this.edit(el, multiline, { kind: "action", action: use, extend: extend[i] });
|
|
2050
2104
|
});
|
|
2051
2105
|
}
|
|
2052
2106
|
/** A field's keymap (SPEC §10.2): the default, then the `data-keys` of
|
|
@@ -2074,6 +2128,7 @@ var Surface = class {
|
|
|
2074
2128
|
};
|
|
2075
2129
|
const start = index(el.selectionStart ?? el.value.length);
|
|
2076
2130
|
const end = index(el.selectionEnd ?? el.value.length);
|
|
2131
|
+
const caret = el.selectionDirection === "backward" ? start : end;
|
|
2077
2132
|
if (what.kind === "type") {
|
|
2078
2133
|
this.goal = null;
|
|
2079
2134
|
this.replace(el, at[start], at[end], what.text);
|
|
@@ -2081,14 +2136,16 @@ var Surface = class {
|
|
|
2081
2136
|
}
|
|
2082
2137
|
const action = what.action;
|
|
2083
2138
|
const password = el.localName === "input" && this.inputType(el) === "password";
|
|
2084
|
-
const text2 = { chars: cl, start, end, multiline, password };
|
|
2139
|
+
const text2 = { chars: cl, start, end, caret, multiline, password };
|
|
2085
2140
|
const g = this.goal;
|
|
2086
|
-
const goal = ROW_ACTIONS.has(action) && g && g.el === el &&
|
|
2141
|
+
const goal = ROW_ACTIONS.has(action) && g && g.el === el && g.caret === caret ? g.x : null;
|
|
2087
2142
|
const rows = multiline && ROW_ACTIONS.has(action) ? this.textareaRows(el, cl) : null;
|
|
2088
|
-
const p = plan(text2, action, rows, goal);
|
|
2143
|
+
const p = plan(text2, action, rows, goal, what.extend);
|
|
2089
2144
|
this.goal = p.kind === "move" && p.goal !== void 0 ? { el, caret: p.to, x: p.goal } : null;
|
|
2090
|
-
if (p.kind === "move")
|
|
2091
|
-
|
|
2145
|
+
if (p.kind === "move") {
|
|
2146
|
+
const a = p.anchor ?? p.to;
|
|
2147
|
+
el.setSelectionRange(at[Math.min(a, p.to)], at[Math.max(a, p.to)], a > p.to ? "backward" : "forward");
|
|
2148
|
+
} else if (p.kind === "replace") this.replace(el, at[p.from], at[p.to], p.text);
|
|
2092
2149
|
}
|
|
2093
2150
|
/** Replaces code units `from` to `to` of a field's value with `text`, the
|
|
2094
2151
|
* caret after it, as typing does. */
|
|
@@ -2177,6 +2234,10 @@ var Surface = class {
|
|
|
2177
2234
|
d.execCommand("insertLineBreak", false);
|
|
2178
2235
|
return;
|
|
2179
2236
|
}
|
|
2237
|
+
if (action === "select-all") {
|
|
2238
|
+
sel.selectAllChildren(host);
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2180
2241
|
const within = () => sel.focusNode !== null && host.contains(sel.focusNode);
|
|
2181
2242
|
const next = (dir) => {
|
|
2182
2243
|
const [an, ao, fn, fo] = [sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset];
|
|
@@ -2186,26 +2247,26 @@ var Surface = class {
|
|
|
2186
2247
|
sel.setBaseAndExtent(an, ao, fn, fo);
|
|
2187
2248
|
return c;
|
|
2188
2249
|
};
|
|
2189
|
-
const walk = (
|
|
2250
|
+
const walk = (alter2, dir, go) => {
|
|
2190
2251
|
for (let i = 0; i < 1e5; i++) {
|
|
2191
2252
|
const c = next(dir);
|
|
2192
2253
|
if (!c || !go(c)) return;
|
|
2193
|
-
sel.modify(
|
|
2254
|
+
sel.modify(alter2, dir, "character");
|
|
2194
2255
|
}
|
|
2195
2256
|
};
|
|
2196
|
-
const back = (
|
|
2257
|
+
const back = (alter2, unit) => {
|
|
2197
2258
|
if (unit === "word") {
|
|
2198
|
-
walk(
|
|
2199
|
-
walk(
|
|
2200
|
-
} else if (unit === "line") walk(
|
|
2201
|
-
else sel.modify(
|
|
2259
|
+
walk(alter2, "backward", isSpace);
|
|
2260
|
+
walk(alter2, "backward", (c) => !isSpace(c));
|
|
2261
|
+
} else if (unit === "line") walk(alter2, "backward", (c) => !isBreak(c));
|
|
2262
|
+
else sel.modify(alter2, "backward", "character");
|
|
2202
2263
|
};
|
|
2203
|
-
const forward = (
|
|
2264
|
+
const forward = (alter2, unit) => {
|
|
2204
2265
|
if (unit === "word") {
|
|
2205
|
-
walk(
|
|
2206
|
-
walk(
|
|
2207
|
-
} else if (unit === "line") walk(
|
|
2208
|
-
else sel.modify(
|
|
2266
|
+
walk(alter2, "forward", isSpace);
|
|
2267
|
+
walk(alter2, "forward", (c) => !isSpace(c));
|
|
2268
|
+
} else if (unit === "line") walk(alter2, "forward", (c) => !isBreak(c));
|
|
2269
|
+
else sel.modify(alter2, "forward", "character");
|
|
2209
2270
|
};
|
|
2210
2271
|
if (action.startsWith("delete-")) {
|
|
2211
2272
|
if (sel.isCollapsed) {
|
|
@@ -2217,28 +2278,29 @@ var Surface = class {
|
|
|
2217
2278
|
return;
|
|
2218
2279
|
}
|
|
2219
2280
|
const backward = ["char-backward", "word-backward", "line-start", "line-previous", "page-up", "input-start"].includes(action);
|
|
2220
|
-
|
|
2281
|
+
const alter = what.extend ? "extend" : "move";
|
|
2282
|
+
if (!what.extend && !sel.isCollapsed) {
|
|
2221
2283
|
if (backward) sel.collapseToStart();
|
|
2222
2284
|
else sel.collapseToEnd();
|
|
2223
2285
|
if (action === "char-backward" || action === "char-forward") return;
|
|
2224
2286
|
}
|
|
2225
2287
|
const by = { "char-backward": "char", "char-forward": "char", "word-backward": "word", "word-forward": "word", "line-start": "line", "line-end": "line" }[action];
|
|
2226
2288
|
if (by) {
|
|
2227
|
-
if (backward) back(
|
|
2228
|
-
else forward(
|
|
2289
|
+
if (backward) back(alter, by);
|
|
2290
|
+
else forward(alter, by);
|
|
2229
2291
|
return;
|
|
2230
2292
|
}
|
|
2231
2293
|
if (action === "input-start" || action === "input-end") {
|
|
2232
|
-
sel.modify(
|
|
2294
|
+
sel.modify(alter, backward ? "backward" : "forward", "documentboundary");
|
|
2233
2295
|
return;
|
|
2234
2296
|
}
|
|
2235
2297
|
if (ROW_ACTIONS.has(action)) {
|
|
2236
2298
|
const rows = action.startsWith("page") ? Math.max(1, Math.round(host.clientHeight / (parseFloat(this.frame.contentWindow.getComputedStyle(host).lineHeight) || 16))) : 1;
|
|
2237
2299
|
for (let i = 0; i < rows; i++) {
|
|
2238
2300
|
const [fn, fo] = [sel.focusNode, sel.focusOffset];
|
|
2239
|
-
sel.modify(
|
|
2301
|
+
sel.modify(alter, backward ? "backward" : "forward", "line");
|
|
2240
2302
|
if (sel.focusNode === fn && sel.focusOffset === fo) {
|
|
2241
|
-
sel.modify(
|
|
2303
|
+
sel.modify(alter, backward ? "backward" : "forward", "documentboundary");
|
|
2242
2304
|
return;
|
|
2243
2305
|
}
|
|
2244
2306
|
}
|
|
@@ -3126,6 +3188,12 @@ function keyText(key) {
|
|
|
3126
3188
|
const v = key.endsWith("++") ? "+" : key.slice(key.lastIndexOf("+") + 1);
|
|
3127
3189
|
return v === "Space" ? " " : v;
|
|
3128
3190
|
}
|
|
3191
|
+
function typesNoText(e) {
|
|
3192
|
+
if (e.getModifierState?.("AltGraph")) return false;
|
|
3193
|
+
if ([...e.key].length === 1) return e.ctrlKey || e.metaKey;
|
|
3194
|
+
return !NOT_KEYS.has(e.key);
|
|
3195
|
+
}
|
|
3196
|
+
var NOT_KEYS = /* @__PURE__ */ new Set(["Unidentified", "Dead", "Process", "Compose", "Shift", "Control", "Alt", "AltGraph", "Meta", "OS", "Super", "Hyper", "Fn", "FnLock", "CapsLock", "NumLock", "ScrollLock", "Symbol", "SymbolLock"]);
|
|
3129
3197
|
function domKeyName(e) {
|
|
3130
3198
|
const char = [...e.key].length === 1;
|
|
3131
3199
|
const mods = [e.ctrlKey && "Control", e.altKey && "Alt", e.metaKey && "Meta", e.shiftKey && !char && "Shift"].filter(Boolean);
|