@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 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
- Each key goes through xterm.js's keyboard handling, with what it would
118
- send the program kept back, and is named from that, so a field sees the
119
- key the program would read, in the encoding the program enabled. The
120
- field's keymap (the default, then each `data-keys` from the root to the
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
@@ -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", "input-start", "input-end", "newline"]);
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 Control+a=line-start End=line-end Control+e=line-end",
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. It is named as the program would read it: from
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 && start === end && g.caret === start ? g.x : null;
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") el.setSelectionRange(at[p.to], at[p.to]);
2091
- else if (p.kind === "replace") this.replace(el, at[p.from], at[p.to], p.text);
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 = (alter, dir, go) => {
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(alter, dir, "character");
2254
+ sel.modify(alter2, dir, "character");
2194
2255
  }
2195
2256
  };
2196
- const back = (alter, unit) => {
2257
+ const back = (alter2, unit) => {
2197
2258
  if (unit === "word") {
2198
- walk(alter, "backward", isSpace);
2199
- walk(alter, "backward", (c) => !isSpace(c));
2200
- } else if (unit === "line") walk(alter, "backward", (c) => !isBreak(c));
2201
- else sel.modify(alter, "backward", "character");
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 = (alter, unit) => {
2264
+ const forward = (alter2, unit) => {
2204
2265
  if (unit === "word") {
2205
- walk(alter, "forward", isSpace);
2206
- walk(alter, "forward", (c) => !isSpace(c));
2207
- } else if (unit === "line") walk(alter, "forward", (c) => !isBreak(c));
2208
- else sel.modify(alter, "forward", "character");
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
- if (!sel.isCollapsed) {
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("move", by);
2228
- else forward("move", by);
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("move", backward ? "backward" : "forward", "documentboundary");
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("move", backward ? "backward" : "forward", "line");
2301
+ sel.modify(alter, backward ? "backward" : "forward", "line");
2240
2302
  if (sel.focusNode === fn && sel.focusOffset === fo) {
2241
- sel.modify("move", backward ? "backward" : "forward", "documentboundary");
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);