@qodalis/cli 2.5.0 → 2.5.2

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/public-api.mjs CHANGED
@@ -518,7 +518,7 @@ var CliKeyValueStore_TOKEN = createCliToken("cli-key-value-store");
518
518
  var SyntaxHighlighterRegistry_TOKEN = createCliToken("syntax-highlighter-registry");
519
519
 
520
520
  // src/lib/version.ts
521
- var LIBRARY_VERSION = "2.5.0";
521
+ var LIBRARY_VERSION = "2.5.2";
522
522
  var API_VERSION = 2;
523
523
  var ANONYMOUS = "\0anonymous";
524
524
  function aliasOwner(context2) {
@@ -21610,6 +21610,27 @@ function parseAddArgs(value) {
21610
21610
  }
21611
21611
  return {};
21612
21612
  }
21613
+ function sameServerUrl(a, b) {
21614
+ const key = (url) => normalizeServerUrl(absoluteUrl(url))?.toLowerCase();
21615
+ const left = key(a);
21616
+ return !!left && left === key(b);
21617
+ }
21618
+ function absoluteUrl(url) {
21619
+ const origin = globalThis.location?.origin;
21620
+ if (!origin || /^[a-z][a-z0-9+.-]*:\/\//i.test(url.trim())) return url;
21621
+ try {
21622
+ return new URL(url.trim() || "/", origin).href;
21623
+ } catch {
21624
+ return url;
21625
+ }
21626
+ }
21627
+ async function serverAt(manager, linked, url) {
21628
+ for (const [name, connection] of manager.connections) {
21629
+ if (!manager.isLinked(name) && sameServerUrl(connection.config.url, url)) return { name, byApp: true };
21630
+ }
21631
+ const saved = (await linked.list()).find((server) => sameServerUrl(server.url, url));
21632
+ return saved ? { name: saved.name, byApp: false } : void 0;
21633
+ }
21613
21634
  async function replaceConnection(manager, config) {
21614
21635
  const current = [...manager.connections.keys()].find((n) => n.toLowerCase() === config.name.toLowerCase());
21615
21636
  const wasDefault = !!current && manager.defaultServer === current;
@@ -21654,6 +21675,17 @@ var ServerAddProcessor = class {
21654
21675
  if (checked.mixedContent) {
21655
21676
  writer.writeWarning(t.t("cli.server.link.mixed_content", "This page is served over https: the browser may block requests to an http server."));
21656
21677
  }
21678
+ const same = await serverAt(manager, linked, url);
21679
+ if (same?.byApp) {
21680
+ writer.writeError(t.t("cli.server.link.same_as_app_server", "{url} is already configured by the app as '{name}'.", { url, name: same.name }));
21681
+ return;
21682
+ }
21683
+ if (same) {
21684
+ writer.writeWarning(t.t("cli.server.link.already_linked", "{url} is already linked as '{name}'.", { url, name: same.name }));
21685
+ const again = await reader.readConfirm(t.t("cli.server.link.link_again", "Link it again? It replaces '{name}' (a new token)", { name: same.name }), false);
21686
+ if (!again) return;
21687
+ }
21688
+ const replacing = same?.name;
21657
21689
  let probe;
21658
21690
  context2.spinner?.show(t.t("cli.server.link.checking", "Checking {url}\u2026", { url }));
21659
21691
  try {
@@ -21670,8 +21702,8 @@ var ServerAddProcessor = class {
21670
21702
  const go = await reader.readConfirm(t.t("cli.server.link.http_confirm", "Link it anyway?"), false);
21671
21703
  if (!go) return;
21672
21704
  }
21673
- let name = args.name;
21674
- if (name) {
21705
+ let name = args.name ?? replacing;
21706
+ if (name && name !== replacing) {
21675
21707
  if (!await this.nameAvailable(context2, manager, linked, name)) return;
21676
21708
  }
21677
21709
  const tokenFlag = command2.args?.["token"];
@@ -21719,6 +21751,10 @@ var ServerAddProcessor = class {
21719
21751
  const server = { name, url, addedAt: Date.now() };
21720
21752
  const previous = (await linked.get(name))?.name;
21721
21753
  if (previous && previous !== name) await linked.remove(previous);
21754
+ if (replacing && replacing.toLowerCase() !== name.toLowerCase()) {
21755
+ await linked.remove(replacing);
21756
+ if (manager.isLinked(replacing)) manager.removeServer(replacing);
21757
+ }
21722
21758
  await linked.save(server, token);
21723
21759
  const connection = await replaceConnection(manager, linkedServerConfig(server, token));
21724
21760
  if (connection.connected) {
@@ -23864,6 +23900,143 @@ function createCompletionProviders(deps) {
23864
23900
  return [...fromPlugins, ...builtIn];
23865
23901
  }
23866
23902
 
23903
+ // src/lib/engine/cli-touch-scroll.ts
23904
+ var SLOP_PX = 8;
23905
+ var MIN_FLING = 0.15;
23906
+ var FRICTION = 0.997;
23907
+ var VELOCITY_WINDOW_MS = 100;
23908
+ var KEYBOARD_HOLD_MS = 400;
23909
+ function enableTouchScroll(terminal, paused = () => false) {
23910
+ const element = terminal.element;
23911
+ if (!element) {
23912
+ return () => {
23913
+ };
23914
+ }
23915
+ let startY = 0;
23916
+ let lastY = 0;
23917
+ let scrolling = false;
23918
+ let carry = 0;
23919
+ let moves = [];
23920
+ let frame = 0;
23921
+ let holdTimer = 0;
23922
+ let holding = false;
23923
+ const textarea = terminal.textarea;
23924
+ const keyboardOpen = () => window.innerHeight < screen.height * 0.75;
23925
+ const holdKeyboard = () => {
23926
+ clearTimeout(holdTimer);
23927
+ if (holding || !textarea || textarea.hasAttribute("inputmode") || keyboardOpen()) {
23928
+ return;
23929
+ }
23930
+ holding = true;
23931
+ textarea.setAttribute("inputmode", "none");
23932
+ };
23933
+ const releaseKeyboard = () => {
23934
+ clearTimeout(holdTimer);
23935
+ if (holding) {
23936
+ holding = false;
23937
+ textarea?.removeAttribute("inputmode");
23938
+ }
23939
+ };
23940
+ const rowHeight = () => {
23941
+ const screen2 = element.querySelector(".xterm-screen");
23942
+ return (screen2?.clientHeight || element.clientHeight) / terminal.rows || 16;
23943
+ };
23944
+ const scrollBy = (pixels) => {
23945
+ carry += pixels;
23946
+ const height = rowHeight();
23947
+ const rows = Math.trunc(carry / height);
23948
+ if (rows === 0) {
23949
+ return true;
23950
+ }
23951
+ carry -= rows * height;
23952
+ const buffer = terminal.buffer.active;
23953
+ const before = buffer.viewportY;
23954
+ terminal.scrollLines(-rows);
23955
+ const moved = buffer.viewportY !== before;
23956
+ if (!moved) {
23957
+ carry = 0;
23958
+ }
23959
+ return moved;
23960
+ };
23961
+ const stopGlide = () => {
23962
+ cancelAnimationFrame(frame);
23963
+ frame = 0;
23964
+ };
23965
+ const glide = (velocity) => {
23966
+ let previous = performance.now();
23967
+ const step = (now) => {
23968
+ const elapsed = Math.min(now - previous, 50);
23969
+ previous = now;
23970
+ velocity *= Math.pow(FRICTION, elapsed);
23971
+ if (Math.abs(velocity) < 0.02 || !scrollBy(velocity * elapsed)) {
23972
+ frame = 0;
23973
+ return;
23974
+ }
23975
+ frame = requestAnimationFrame(step);
23976
+ };
23977
+ frame = requestAnimationFrame(step);
23978
+ };
23979
+ const onStart = (e) => {
23980
+ stopGlide();
23981
+ scrolling = false;
23982
+ if (e.touches.length !== 1 || paused()) {
23983
+ return;
23984
+ }
23985
+ holdKeyboard();
23986
+ startY = lastY = e.touches[0].clientY;
23987
+ carry = 0;
23988
+ moves = [{ y: startY, time: e.timeStamp }];
23989
+ };
23990
+ const onMove = (e) => {
23991
+ if (e.touches.length !== 1 || paused() || moves.length === 0) {
23992
+ return;
23993
+ }
23994
+ const y = e.touches[0].clientY;
23995
+ if (!scrolling && Math.abs(y - startY) < SLOP_PX) {
23996
+ return;
23997
+ }
23998
+ scrolling = true;
23999
+ e.preventDefault();
24000
+ scrollBy(y - lastY);
24001
+ lastY = y;
24002
+ moves.push({ y, time: e.timeStamp });
24003
+ while (moves.length > 2 && e.timeStamp - moves[0].time > VELOCITY_WINDOW_MS) {
24004
+ moves.shift();
24005
+ }
24006
+ };
24007
+ const onEnd = (e) => {
24008
+ if (!scrolling) {
24009
+ releaseKeyboard();
24010
+ moves = [];
24011
+ return;
24012
+ }
24013
+ e.preventDefault();
24014
+ scrolling = false;
24015
+ holdTimer = window.setTimeout(releaseKeyboard, KEYBOARD_HOLD_MS);
24016
+ const first = moves[0];
24017
+ const last = moves[moves.length - 1];
24018
+ moves = [];
24019
+ const elapsed = last.time - first.time;
24020
+ const velocity = elapsed > 0 && e.timeStamp - last.time < VELOCITY_WINDOW_MS ? (last.y - first.y) / elapsed : 0;
24021
+ if (Math.abs(velocity) >= MIN_FLING) {
24022
+ glide(velocity);
24023
+ }
24024
+ };
24025
+ const options = { passive: false, capture: true };
24026
+ element.addEventListener("touchstart", onStart, options);
24027
+ element.addEventListener("touchmove", onMove, options);
24028
+ element.addEventListener("touchend", onEnd, options);
24029
+ element.addEventListener("touchcancel", onEnd, options);
24030
+ return () => {
24031
+ stopGlide();
24032
+ releaseKeyboard();
24033
+ element.removeEventListener("touchstart", onStart, options);
24034
+ element.removeEventListener("touchmove", onMove, options);
24035
+ element.removeEventListener("touchend", onEnd, options);
24036
+ element.removeEventListener("touchcancel", onEnd, options);
24037
+ };
24038
+ }
24039
+
23867
24040
  // src/lib/engine/cli-engine.ts
23868
24041
  var CliEngine = class {
23869
24042
  constructor(container, options) {
@@ -23975,6 +24148,12 @@ var CliEngine = class {
23975
24148
  this.serializeAddon = setup.serializeAddon;
23976
24149
  this.dragDropService = setup.dragDropService;
23977
24150
  this.wheelListener = setup.wheelListener;
24151
+ if (this.options?.touchScroll !== false) {
24152
+ this.disposeTouchScroll = enableTouchScroll(
24153
+ this.terminal,
24154
+ () => !!this.executionContext?.contextProcessor
24155
+ );
24156
+ }
23978
24157
  this.handleResize();
23979
24158
  }
23980
24159
  /** 2. Service container, boot service, executor and execution context. */
@@ -24204,6 +24383,7 @@ var CliEngine = class {
24204
24383
  if (this.wheelListener) {
24205
24384
  this.container.removeEventListener("wheel", this.wheelListener);
24206
24385
  }
24386
+ this.disposeTouchScroll?.();
24207
24387
  this.resizeVersion = -1;
24208
24388
  this.resizeScheduled = false;
24209
24389
  this.resizeObserver?.disconnect();
@@ -25112,6 +25292,21 @@ var DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
25112
25292
  function asEventTarget(node) {
25113
25293
  return node && typeof node.addEventListener === "function" ? node : null;
25114
25294
  }
25295
+ function canScrollWithin(target, root, dx, dy) {
25296
+ for (let el = target; el && el !== root; el = el.parentElement) {
25297
+ if (!(el instanceof HTMLElement)) continue;
25298
+ const style = getComputedStyle(el);
25299
+ const scrollsY = /auto|scroll/.test(style.overflowY) && el.scrollHeight > el.clientHeight;
25300
+ const scrollsX = /auto|scroll/.test(style.overflowX) && el.scrollWidth > el.clientWidth;
25301
+ if (dy !== 0 && scrollsY) {
25302
+ if (dy < 0 ? el.scrollTop > 0 : el.scrollTop + el.clientHeight < el.scrollHeight) return true;
25303
+ }
25304
+ if (dx !== 0 && scrollsX) {
25305
+ if (dx < 0 ? el.scrollLeft > 0 : el.scrollLeft + el.clientWidth < el.scrollWidth) return true;
25306
+ }
25307
+ }
25308
+ return false;
25309
+ }
25115
25310
  function followsSystem(input) {
25116
25311
  return input === "auto" || typeof input === "object" && input !== null && input.base === "auto";
25117
25312
  }
@@ -25193,6 +25388,16 @@ var CliPanelController = class {
25193
25388
  if (event.target !== first) return;
25194
25389
  this.syncThemeFromEngine(event.detail);
25195
25390
  };
25391
+ /**
25392
+ * A wheel over the panel chrome (header, icon strip, empty pane) would
25393
+ * scroll the page behind it; the terminals already keep their own wheel.
25394
+ */
25395
+ this.onPanelWheel = (event) => {
25396
+ const wheel = event;
25397
+ const root = this.rootElement;
25398
+ if (!root || canScrollWithin(wheel.target, root, wheel.deltaX, wheel.deltaY)) return;
25399
+ wheel.preventDefault();
25400
+ };
25196
25401
  this.onVisualViewportChange = () => {
25197
25402
  const fields = visualViewportFields();
25198
25403
  const s = this.snapshot;
@@ -25301,9 +25506,13 @@ var CliPanelController = class {
25301
25506
  /** Element containing the terminals, used for theme sync. */
25302
25507
  setRootElement(element) {
25303
25508
  if (element === this.rootElement) return;
25304
- asEventTarget(this.rootElement)?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25509
+ const previous = asEventTarget(this.rootElement);
25510
+ previous?.removeEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25511
+ previous?.removeEventListener("wheel", this.onPanelWheel);
25305
25512
  this.rootElement = element;
25306
- asEventTarget(element)?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25513
+ const next = asEventTarget(element);
25514
+ next?.addEventListener(CLI_TERMINAL_THEME_EVENT, this.onTerminalTheme);
25515
+ next?.addEventListener("wheel", this.onPanelWheel, { passive: false });
25307
25516
  if (element && this.followsTerminalTheme) this.startThemeSync();
25308
25517
  }
25309
25518
  /**